-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Changes from 52 commits
f3a6ecb
6897f6b
24821d5
9a7a17a
7d92baf
2a1ae88
54a5dcb
4f52651
ad69825
bf534cd
53e6f17
4f660e6
ccd03c6
5e6ef51
a42fa75
f123c64
c4bb231
8404ee0
85e350b
148d3bf
cfdb146
efed301
5c9c3d9
f69d367
e422fc0
489fc24
f859805
7c2b545
e2bc881
a9a8b57
6efbf89
0ff7bec
cd1e260
59adb59
6a95dc2
99b9619
30c619f
48abd24
f07c166
619ae2e
f5f382b
e583cc1
734d5ad
3fef30d
ccc3b01
8776fa6
e5e8da0
1b4b1b4
128e6e8
e9c22f9
6ac1ba7
49a19fe
d44420c
db9877c
a9bc09e
26f49a4
bf282f9
27254d4
5526de0
664dced
47e4b62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
"use strict"; | ||
|
||
const editorconfig = require("editorconfig"); | ||
const mem = require("mem"); | ||
const pathRoot = require("path-root"); | ||
|
||
const maybeParse = (filePath, config, parse) => { | ||
const root = filePath && pathRoot(filePath); | ||
return filePath && !config && parse(filePath, { root }); | ||
}; | ||
|
||
const editorconfigAsyncNoCache = (filePath, config) => { | ||
return Promise.resolve(maybeParse(filePath, config, editorconfig.parse)).then( | ||
editorConfigToPrettier | ||
); | ||
}; | ||
const editorconfigAsyncWithCache = mem(editorconfigAsyncNoCache); | ||
|
||
const editorconfigSyncNoCache = (filePath, config) => { | ||
return editorConfigToPrettier( | ||
maybeParse(filePath, config, editorconfig.parseSync) | ||
); | ||
}; | ||
const editorconfigSyncWithCache = mem(editorconfigSyncNoCache); | ||
|
||
function getLoadFunction(opts) { | ||
if (opts.sync) { | ||
return opts.cache ? editorconfigSyncWithCache : editorconfigSyncNoCache; | ||
} | ||
|
||
return opts.cache ? editorconfigAsyncWithCache : editorconfigAsyncNoCache; | ||
} | ||
|
||
function clearCache() { | ||
mem.clear(editorconfigSyncWithCache); | ||
mem.clear(editorconfigAsyncWithCache); | ||
} | ||
|
||
function editorConfigToPrettier(editorConfig) { | ||
editorConfig = 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; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
module.exports = { | ||
getLoadFunction, | ||
clearCache | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,8 @@ const minimatch = require("minimatch"); | |
const path = require("path"); | ||
const mem = require("mem"); | ||
|
||
const resolveEditorConfig = require("./resolve-config-editorconfig"); | ||
|
||
const getExplorerMemoized = mem(opts => | ||
cosmiconfig("prettier", { | ||
sync: opts.sync, | ||
|
@@ -20,23 +22,43 @@ function getLoadFunction(opts) { | |
return getExplorerMemoized(opts).load; | ||
} | ||
|
||
function resolveConfig(filePath, opts) { | ||
function _resolveConfig(filePath, opts, sync) { | ||
opts = Object.assign({ useCache: true }, opts); | ||
const load = getLoadFunction({ cache: !!opts.useCache, sync: false }); | ||
return load(filePath, opts.config).then(result => { | ||
return !result ? null : mergeOverrides(result, filePath); | ||
}); | ||
const loadOpts = { cache: !!opts.useCache, sync: !!sync }; | ||
const load = getLoadFunction(loadOpts); | ||
const loadEditorConfig = resolveEditorConfig.getLoadFunction(loadOpts); | ||
const arr = [load, loadEditorConfig].map(l => l(filePath, opts.config)); | ||
|
||
const unwrapAndMerge = arr => { | ||
const result = arr[0]; | ||
const editorConfigured = arr[1]; | ||
const merged = Object.assign( | ||
{}, | ||
editorConfigured, | ||
mergeOverrides(Object.assign({}, result), filePath) | ||
); | ||
|
||
if (Object.keys(merged).length === 0) { | ||
return null; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if we should treat 0-option as not-found, but if so, we should document it on the README as well. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm thinking that we shouldn't distinguish between the cases where I just clarified this in d44420c There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was thinking in using this difference to test for the existence of a
@robwise May have implemented it this way (atom). Anyway, if it also takes There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes we definitely want to keep this distinction. An empty config file is different to no config file. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Right now I can't tell the difference between an empty config file and a missing one, so I have to put a special note to users that they need to have a rule in it in order for it to be picked up: It would be nice to have a way to differentiate so we won't have this edge case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the feedback, everyone! I see why being able to differentiate between these cases is useful, so I'll try to add some patches implementing/testing this. EDIT: Oh, @CiGit:
I assume you're talking about
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I thought about this a bit more, and I'd actually like to let it be part of a separate PR later, since it's a pre-existing issue. That way, this PR can focus on the editorconfig functionality. |
||
} | ||
|
||
return merged; | ||
}; | ||
|
||
if (loadOpts.sync) { | ||
return unwrapAndMerge(arr); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it important that the 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Yes, I think that's what fixed this test: https://travis-ci.org/prettier/prettier/jobs/272596635#L379
Hmm, it looks like tab_width and max_line_length must be positive. I suppose someone could set There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess it's all fine then 👍 |
||
return Promise.all(arr).then(unwrapAndMerge); | ||
} | ||
|
||
resolveConfig.sync = (filePath, opts) => { | ||
opts = Object.assign({ useCache: true }, opts); | ||
const load = getLoadFunction({ cache: !!opts.useCache, sync: true }); | ||
const result = load(filePath, opts.config); | ||
return !result ? null : mergeOverrides(result, filePath); | ||
}; | ||
const resolveConfig = (filePath, opts) => _resolveConfig(filePath, opts, false); | ||
|
||
resolveConfig.sync = (filePath, opts) => _resolveConfig(filePath, opts, true); | ||
|
||
function clearCache() { | ||
mem.clear(getExplorerMemoized); | ||
resolveEditorConfig.clearCache(); | ||
} | ||
|
||
function resolveConfigFile(filePath) { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -102,6 +102,74 @@ test("API resolveConfig.sync with file arg and extension override", () => { | |
}); | ||
}); | ||
|
||
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 | ||
}); | ||
}); | ||
|
||
test("API resolveConfig with missing file arg", () => { | ||
const file = path.resolve( | ||
path.join(__dirname, "../cli/config/editorconfig/file.shouldnotexist") | ||
); | ||
return prettier.resolveConfig(file).then(result => { | ||
expect(result).toBeNull(); | ||
}); | ||
}); | ||
|
||
test("API resolveConfig.sync with missing file arg", () => { | ||
const file = path.resolve( | ||
path.join(__dirname, "../cli/config/editorconfig/file.shouldnotexist") | ||
); | ||
expect(prettier.resolveConfig.sync(file)).toBeNull(); | ||
}); | ||
|
||
test("API clearConfigCache", () => { | ||
expect(() => prettier.clearConfigCache()).not.toThrowError(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is somewhat out of scope for this PR, but it's an interesting discussion: Are we happy with this test, or do we want to do something like:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That sounds pretty good to me. We could add another step before 3 (changes in bold)
I'd like to do this in a separate PR, though, at least for the non-editorconfig caches (and maybe the editorconfig caches too) |
||
}); | ||
|
||
test("API resolveConfig.sync overrides work with absolute paths", () => { | ||
// Absolute path | ||
const file = path.join(__dirname, "../cli/config/filepath/subfolder/file.js"); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
semi: false | ||
|
||
overrides: | ||
- files: "*.js" | ||
options: | ||
semi: false | ||
- files: "*.ts" | ||
options: | ||
semi: true |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
root = true | ||
|
||
[*.js] | ||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
function f() { | ||
console.log("should have tab width 8"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
function f() { | ||
console.log("should have space width 2"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# This file should be overridden by prettier.config.js | ||
|
||
[*] | ||
tab_width = 1 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# This file should be overridden by package.json | ||
|
||
[*] | ||
tab_width = 1 | ||
|
||
[*.ts] | ||
tab_width = 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's an edge case, but technically I believe it's possible to define both an
indent_size
and atab_width
, where the latter should be preferred in caseindent_style
istab
. In fact,indent_size
can actually be set totab
. This line would, therefore, get the wrong prettiertabWidth
or break completely?https://github.com/prettier/prettier-atom/blob/master/src/executePrettier/buildEditorConfigOptions.test.js#L15-L21