Skip to content

Commit 8f58ca0

Browse files
Respect EditorConfig settings (#2760)
This fixes #42. It adds support for .editorconfig's `indent_style`, `indent_size`, `tab_width`, and `max_line_length` properties. It doesn't support the `end_of_line` property as described in #42 (comment), but that could be added later. The same goes for `quote_type` (prettier/prettier-atom#293 (comment)). * Make test .prettierrc not set config for all file extensions This makes it easier to keep tests isolated.
1 parent 9652ad7 commit 8f58ca0

15 files changed

Lines changed: 315 additions & 19 deletions

docs/configuration.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,11 @@ For more information on how to use the CLI to locate a file, see the [CLI](cli.m
8383
## Configuration Schema
8484

8585
If you'd like a JSON schema to validate your configuration, one is available here: http://json.schemastore.org/prettierrc.
86+
87+
## EditorConfig
88+
89+
If an [`.editorconfig` file](http://editorconfig.org/) is in your project, Prettier will parse it and convert its properties to the corresponding prettier configuration. This configuration will be overridden by `.prettierrc`, etc. Currently, the following EditorConfig properties are supported:
90+
91+
* `indent_style`
92+
* `indent_size`/`tab_width`
93+
* `max_line_length`

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
"cosmiconfig": "3.1.0",
2323
"dashify": "0.2.2",
2424
"diff": "3.2.0",
25+
"editorconfig": "0.14.2",
26+
"editorconfig-to-prettier": "0.0.1",
2527
"emoji-regex": "6.5.1",
2628
"escape-string-regexp": "1.0.5",
2729
"esutils": "2.0.2",
@@ -37,6 +39,7 @@
3739
"minimatch": "3.0.4",
3840
"minimist": "1.2.0",
3941
"parse5": "3.0.3",
42+
"path-root": "0.1.1",
4043
"postcss-less": "1.1.1",
4144
"postcss-media-query-parser": "0.2.3",
4245
"postcss-scss": "1.0.2",

src/resolve-config-editorconfig.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
"use strict";
2+
3+
const editorconfig = require("editorconfig");
4+
const mem = require("mem");
5+
const pathRoot = require("path-root");
6+
const editorConfigToPrettier = require("editorconfig-to-prettier");
7+
8+
const maybeParse = (filePath, config, parse) => {
9+
const root = filePath && pathRoot(filePath);
10+
return filePath && !config && parse(filePath, { root });
11+
};
12+
13+
const editorconfigAsyncNoCache = (filePath, config) => {
14+
return Promise.resolve(maybeParse(filePath, config, editorconfig.parse)).then(
15+
editorConfigToPrettier
16+
);
17+
};
18+
const editorconfigAsyncWithCache = mem(editorconfigAsyncNoCache);
19+
20+
const editorconfigSyncNoCache = (filePath, config) => {
21+
return editorConfigToPrettier(
22+
maybeParse(filePath, config, editorconfig.parseSync)
23+
);
24+
};
25+
const editorconfigSyncWithCache = mem(editorconfigSyncNoCache);
26+
27+
function getLoadFunction(opts) {
28+
if (opts.sync) {
29+
return opts.cache ? editorconfigSyncWithCache : editorconfigSyncNoCache;
30+
}
31+
32+
return opts.cache ? editorconfigAsyncWithCache : editorconfigAsyncNoCache;
33+
}
34+
35+
function clearCache() {
36+
mem.clear(editorconfigSyncWithCache);
37+
mem.clear(editorconfigAsyncWithCache);
38+
}
39+
40+
module.exports = {
41+
getLoadFunction,
42+
clearCache
43+
};

src/resolve-config.js

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ const minimatch = require("minimatch");
55
const path = require("path");
66
const mem = require("mem");
77

8+
const resolveEditorConfig = require("./resolve-config-editorconfig");
9+
810
const getExplorerMemoized = mem(opts =>
911
cosmiconfig("prettier", {
1012
sync: opts.sync,
@@ -26,23 +28,43 @@ function getLoadFunction(opts) {
2628
return getExplorerMemoized(opts).load;
2729
}
2830

29-
function resolveConfig(filePath, opts) {
31+
function _resolveConfig(filePath, opts, sync) {
3032
opts = Object.assign({ useCache: true }, opts);
31-
const load = getLoadFunction({ cache: !!opts.useCache, sync: false });
32-
return load(filePath, opts.config).then(result => {
33-
return !result ? null : mergeOverrides(result, filePath);
34-
});
33+
const loadOpts = { cache: !!opts.useCache, sync: !!sync };
34+
const load = getLoadFunction(loadOpts);
35+
const loadEditorConfig = resolveEditorConfig.getLoadFunction(loadOpts);
36+
const arr = [load, loadEditorConfig].map(l => l(filePath, opts.config));
37+
38+
const unwrapAndMerge = arr => {
39+
const result = arr[0];
40+
const editorConfigured = arr[1];
41+
const merged = Object.assign(
42+
{},
43+
editorConfigured,
44+
mergeOverrides(Object.assign({}, result), filePath)
45+
);
46+
47+
if (Object.keys(merged).length === 0) {
48+
return null;
49+
}
50+
51+
return merged;
52+
};
53+
54+
if (loadOpts.sync) {
55+
return unwrapAndMerge(arr);
56+
}
57+
58+
return Promise.all(arr).then(unwrapAndMerge);
3559
}
3660

37-
resolveConfig.sync = (filePath, opts) => {
38-
opts = Object.assign({ useCache: true }, opts);
39-
const load = getLoadFunction({ cache: !!opts.useCache, sync: true });
40-
const result = load(filePath, opts.config);
41-
return !result ? null : mergeOverrides(result, filePath);
42-
};
61+
const resolveConfig = (filePath, opts) => _resolveConfig(filePath, opts, false);
62+
63+
resolveConfig.sync = (filePath, opts) => _resolveConfig(filePath, opts, true);
4364

4465
function clearCache() {
4566
mem.clear(getExplorerMemoized);
67+
resolveEditorConfig.clearCache();
4668
}
4769

4870
function resolveConfigFile(filePath) {

tests_integration/__tests__/__snapshots__/config-resolution.js.snap

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,22 @@
33
exports[`CLI overrides take precedence (stderr) 1`] = `""`;
44

55
exports[`CLI overrides take precedence (stdout) 1`] = `
6-
"console.log(
6+
"function f() {
7+
console.log(
8+
\\"should have tab width 8\\"
9+
)
10+
}
11+
function f() {
12+
console.log(
13+
\\"should have space width 2\\"
14+
)
15+
}
16+
function f() {
17+
console.log(
18+
\\"should have space width 8\\"
19+
)
20+
}
21+
console.log(
722
\\"jest/__best-tests__/file.js should have semi\\"
823
);
924
console.log(
@@ -84,7 +99,16 @@ exports[`resolves configuration file with --find-config-path file (write) 1`] =
8499
exports[`resolves configuration from external files (stderr) 1`] = `""`;
85100

86101
exports[`resolves configuration from external files (stdout) 1`] = `
87-
"console.log(\\"jest/__best-tests__/file.js should have semi\\");
102+
"function f() {
103+
console.log(\\"should have tab width 8\\")
104+
}
105+
function f() {
106+
console.log(\\"should have space width 2\\")
107+
}
108+
function f() {
109+
console.log(\\"should have space width 8\\")
110+
}
111+
console.log(\\"jest/__best-tests__/file.js should have semi\\");
88112
console.log(\\"jest/Component.js should not have semi\\")
89113
console.log(\\"jest/Component.test.js should have semi\\");
90114
function js() {

tests_integration/__tests__/__snapshots__/with-config-precedence.js.snap

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,22 @@ exports[`CLI overrides take lower precedence with --config-precedence file-overr
8080
exports[`CLI overrides take precedence with --config-precedence cli-override (stderr) 1`] = `""`;
8181
8282
exports[`CLI overrides take precedence with --config-precedence cli-override (stdout) 1`] = `
83-
"console.log(
83+
"function f() {
84+
console.log(
85+
\\"should have tab width 8\\"
86+
)
87+
}
88+
function f() {
89+
console.log(
90+
\\"should have space width 2\\"
91+
)
92+
}
93+
function f() {
94+
console.log(
95+
\\"should have space width 8\\"
96+
)
97+
}
98+
console.log(
8499
\\"jest/__best-tests__/file.js should have semi\\"
85100
);
86101
console.log(
@@ -137,7 +152,22 @@ exports[`CLI overrides take precedence with --config-precedence cli-override (wr
137152
exports[`CLI overrides take precedence without --config-precedence (stderr) 1`] = `""`;
138153
139154
exports[`CLI overrides take precedence without --config-precedence (stdout) 1`] = `
140-
"console.log(
155+
"function f() {
156+
console.log(
157+
\\"should have tab width 8\\"
158+
)
159+
}
160+
function f() {
161+
console.log(
162+
\\"should have space width 2\\"
163+
)
164+
}
165+
function f() {
166+
console.log(
167+
\\"should have space width 8\\"
168+
)
169+
}
170+
console.log(
141171
\\"jest/__best-tests__/file.js should have semi\\"
142172
);
143173
console.log(

tests_integration/__tests__/config-resolution.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,98 @@ test("API resolveConfig.sync with file arg and extension override", () => {
102102
});
103103
});
104104

105+
test("API resolveConfig with file arg and .editorconfig", () => {
106+
const file = path.resolve(
107+
path.join(__dirname, "../cli/config/editorconfig/file.js")
108+
);
109+
return prettier.resolveConfig(file).then(result => {
110+
expect(result).toMatchObject({
111+
useTabs: true,
112+
tabWidth: 8,
113+
printWidth: 100
114+
});
115+
});
116+
});
117+
118+
test("API resolveConfig.sync with file arg and .editorconfig", () => {
119+
const file = path.resolve(
120+
path.join(__dirname, "../cli/config/editorconfig/file.js")
121+
);
122+
expect(prettier.resolveConfig.sync(file)).toMatchObject({
123+
useTabs: true,
124+
tabWidth: 8,
125+
printWidth: 100
126+
});
127+
});
128+
129+
test("API resolveConfig with nested file arg and .editorconfig", () => {
130+
const file = path.resolve(
131+
path.join(__dirname, "../cli/config/editorconfig/lib/file.js")
132+
);
133+
return prettier.resolveConfig(file).then(result => {
134+
expect(result).toMatchObject({
135+
useTabs: false,
136+
tabWidth: 2,
137+
printWidth: 100
138+
});
139+
});
140+
});
141+
142+
test("API resolveConfig.sync with nested file arg and .editorconfig", () => {
143+
const file = path.resolve(
144+
path.join(__dirname, "../cli/config/editorconfig/lib/file.js")
145+
);
146+
expect(prettier.resolveConfig.sync(file)).toMatchObject({
147+
useTabs: false,
148+
tabWidth: 2,
149+
printWidth: 100
150+
});
151+
});
152+
153+
test("API resolveConfig with nested file arg and .editorconfig and indent_size = tab", () => {
154+
const file = path.resolve(
155+
path.join(__dirname, "../cli/config/editorconfig/lib/indent_size=tab.js")
156+
);
157+
return prettier.resolveConfig(file).then(result => {
158+
expect(result).toMatchObject({
159+
useTabs: false,
160+
tabWidth: 8,
161+
printWidth: 100
162+
});
163+
});
164+
});
165+
166+
test("API resolveConfig.sync with nested file arg and .editorconfig and indent_size = tab", () => {
167+
const file = path.resolve(
168+
path.join(__dirname, "../cli/config/editorconfig/lib/indent_size=tab.js")
169+
);
170+
expect(prettier.resolveConfig.sync(file)).toMatchObject({
171+
useTabs: false,
172+
tabWidth: 8,
173+
printWidth: 100
174+
});
175+
});
176+
177+
test("API resolveConfig with missing file arg", () => {
178+
const file = path.resolve(
179+
path.join(__dirname, "../cli/config/editorconfig/file.shouldnotexist")
180+
);
181+
return prettier.resolveConfig(file).then(result => {
182+
expect(result).toBeNull();
183+
});
184+
});
185+
186+
test("API resolveConfig.sync with missing file arg", () => {
187+
const file = path.resolve(
188+
path.join(__dirname, "../cli/config/editorconfig/file.shouldnotexist")
189+
);
190+
expect(prettier.resolveConfig.sync(file)).toBeNull();
191+
});
192+
193+
test("API clearConfigCache", () => {
194+
expect(() => prettier.clearConfigCache()).not.toThrowError();
195+
});
196+
105197
test("API resolveConfig.sync overrides work with absolute paths", () => {
106198
// Absolute path
107199
const file = path.join(__dirname, "../cli/config/filepath/subfolder/file.js");
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
semi: false
2-
31
overrides:
2+
- files: "*.js"
3+
options:
4+
semi: false
45
- files: "*.ts"
56
options:
67
semi: true
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
root = true
2+
3+
[*.js]
4+
indent_style = tab
5+
tab_width = 8
6+
indent_size = 2 # overridden by tab_width since indent_style = tab
7+
max_line_length = 100
8+
9+
# Indentation override for all JS under lib directory
10+
[lib/**.js]
11+
indent_style = space
12+
indent_size = 2
13+
14+
[lib/indent_size=tab.js]
15+
indent_size = tab
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function f() {
2+
console.log("should have tab width 8");
3+
}

0 commit comments

Comments
 (0)