Skip to content

Commit c0ad58b

Browse files
authored
feat: use Prettier to detect end of line in addition to EditorConfig (#803)
* refactor: move to EOL * refactor: getNodeEOL * update * make async * fix tests * update * refactor tests * add third test * changing snapshot name * update * update * fix tests * fix coverage
1 parent a90332f commit c0ad58b

File tree

9 files changed

+420
-308
lines changed

9 files changed

+420
-308
lines changed

lib/context.ts

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { EOL } from 'node:os';
2-
import editorconfig from 'editorconfig';
1+
import { getEndOfLine } from './eol.js';
32

43
/**
54
* Context about the current invocation of the program, like what end-of-line
@@ -9,29 +8,8 @@ export interface Context {
98
endOfLine: string;
109
}
1110

12-
export function getContext(): Context {
11+
export async function getContext(): Promise<Context> {
1312
return {
14-
endOfLine: getEndOfLine(),
13+
endOfLine: await getEndOfLine(),
1514
};
1615
}
17-
18-
/**
19-
* Gets the end of line string while respecting the `.editorconfig` and falling
20-
* back to `EOL` from `node:os`.
21-
*/
22-
export function getEndOfLine() {
23-
// The passed `markdown.md` argument is used as an example of a markdown file
24-
// in the plugin root folder in order to check for any specific markdown
25-
// configurations.
26-
const config = editorconfig.parseSync('markdown.md');
27-
28-
if (config.end_of_line === 'lf') {
29-
return '\n';
30-
}
31-
32-
if (config.end_of_line === 'crlf') {
33-
return '\r\n';
34-
}
35-
36-
return EOL;
37-
}

lib/eol.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { EOL } from 'node:os';
2+
import editorconfig from 'editorconfig';
3+
4+
export async function getEndOfLine(): Promise<'\n' | '\r\n'> {
5+
return (
6+
(await getEndOfLineFromEditorConfig()) ??
7+
(await getEndOfLineFromPrettierConfig()) ??
8+
getNodeEOL()
9+
);
10+
}
11+
12+
async function getEndOfLineFromEditorConfig(): Promise<
13+
'\n' | '\r\n' | undefined
14+
> {
15+
// The passed `markdown.md` argument is used as an example of a Markdown file
16+
// in the plugin root folder in order to check for any specific Markdown
17+
// configurations.
18+
const editorConfigProps = await editorconfig.parse('markdown.md');
19+
20+
if (editorConfigProps.end_of_line === 'lf') {
21+
return '\n';
22+
}
23+
24+
if (editorConfigProps.end_of_line === 'crlf') {
25+
return '\r\n';
26+
}
27+
28+
return undefined;
29+
}
30+
31+
async function getEndOfLineFromPrettierConfig(): Promise<
32+
'\n' | '\r\n' | undefined
33+
> {
34+
let prettier: typeof import('prettier') | undefined;
35+
try {
36+
prettier = await import('prettier');
37+
} catch {
38+
/* istanbul ignore next */
39+
return undefined;
40+
}
41+
42+
// The passed `markdown.md` argument is used as an example of a Markdown file
43+
// in the plugin root folder in order to check for any specific Markdown
44+
// configurations.
45+
const prettierOptions = await prettier.resolveConfig('markdown.md');
46+
47+
if (prettierOptions === null) {
48+
return undefined;
49+
}
50+
51+
if (prettierOptions.endOfLine === 'lf') {
52+
return '\n';
53+
}
54+
55+
if (prettierOptions.endOfLine === 'crlf') {
56+
return '\r\n';
57+
}
58+
59+
// Prettier defaults to "lf" if it is not explicitly specified in the config file:
60+
// https://prettier.io/docs/options#end-of-line
61+
return '\n';
62+
}
63+
64+
/* istanbul ignore next */
65+
/** `EOL` is typed as `string`, so we perform run-time validation to be safe. */
66+
function getNodeEOL(): '\n' | '\r\n' {
67+
if (EOL === '\n' || EOL === '\r\n') {
68+
return EOL;
69+
}
70+
71+
throw new Error(
72+
`Failed to detect the end-of-line constant from the JavaScript runtime: ${EOL}`,
73+
);
74+
}

lib/generator.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ function stringOrArrayToArrayWithFallback(
6363

6464
// eslint-disable-next-line complexity
6565
export async function generate(path: string, options?: GenerateOptions) {
66-
const context = getContext();
66+
const context = await getContext();
6767
const { endOfLine } = context;
6868

6969
const plugin = await loadPlugin(path);

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@
8787
"typescript-eslint": "^8.0.0"
8888
},
8989
"peerDependencies": {
90-
"eslint": ">= 8.57.1"
90+
"eslint": ">= 8.57.1",
91+
"prettier": ">= 3.0.0"
9192
},
9293
"engines": {
9394
"node": "^18.18.0 || ^20.9.0 || >=22.0.0"

test/lib/generate/__snapshots__/editor-config-test.ts.snap

Lines changed: 0 additions & 118 deletions
This file was deleted.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using crlf end of line from ".editorconfig" 1`] = `
4+
"## Rules
5+
<!-- begin auto-generated rules list -->
6+
7+
💼 Configurations enabled in.
8+
9+
| Name | 💼 |
10+
| :------------------- | :------------------------------------- |
11+
| [a](docs/rules/a.md) | ![badge-a][] ![badge-B][] ![badge-c][] |
12+
| [B](docs/rules/B.md) | |
13+
| [c](docs/rules/c.md) | |
14+
15+
<!-- end auto-generated rules list -->
16+
"
17+
`;
18+
19+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using crlf end of line from ".editorconfig" 2`] = `
20+
"# test/a
21+
22+
💼 This rule is enabled in the following configs: \`a\`, \`B\`, \`c\`.
23+
24+
<!-- end auto-generated rule header -->
25+
"
26+
`;
27+
28+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using crlf end of line from ".editorconfig" 3`] = `
29+
"# test/B
30+
31+
<!-- end auto-generated rule header -->
32+
"
33+
`;
34+
35+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using crlf end of line from ".editorconfig" 4`] = `
36+
"# test/c
37+
38+
<!-- end auto-generated rule header -->
39+
"
40+
`;
41+
42+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using lf end of line from ".editorconfig" 1`] = `
43+
"## Rules
44+
<!-- begin auto-generated rules list -->
45+
46+
💼 Configurations enabled in.
47+
48+
| Name | 💼 |
49+
| :------------------- | :------------------------------------- |
50+
| [a](docs/rules/a.md) | ![badge-a][] ![badge-B][] ![badge-c][] |
51+
| [B](docs/rules/B.md) | |
52+
| [c](docs/rules/c.md) | |
53+
54+
<!-- end auto-generated rules list -->
55+
"
56+
`;
57+
58+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using lf end of line from ".editorconfig" 2`] = `
59+
"# test/a
60+
61+
💼 This rule is enabled in the following configs: \`a\`, \`B\`, \`c\`.
62+
63+
<!-- end auto-generated rule header -->
64+
"
65+
`;
66+
67+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using lf end of line from ".editorconfig" 3`] = `
68+
"# test/B
69+
70+
<!-- end auto-generated rule header -->
71+
"
72+
`;
73+
74+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using lf end of line from ".editorconfig" 4`] = `
75+
"# test/c
76+
77+
<!-- end auto-generated rule header -->
78+
"
79+
`;
80+
81+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using the end of line from ".editorconfig" while respecting the .md specific end of line setting 1`] = `
82+
"## Rules
83+
<!-- begin auto-generated rules list -->
84+
85+
💼 Configurations enabled in.
86+
87+
| Name | 💼 |
88+
| :------------------- | :------------------------------------- |
89+
| [a](docs/rules/a.md) | ![badge-a][] ![badge-B][] ![badge-c][] |
90+
| [B](docs/rules/B.md) | |
91+
| [c](docs/rules/c.md) | |
92+
93+
<!-- end auto-generated rules list -->
94+
"
95+
`;
96+
97+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using the end of line from ".editorconfig" while respecting the .md specific end of line setting 2`] = `
98+
"# test/a
99+
100+
💼 This rule is enabled in the following configs: \`a\`, \`B\`, \`c\`.
101+
102+
<!-- end auto-generated rule header -->
103+
"
104+
`;
105+
106+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using the end of line from ".editorconfig" while respecting the .md specific end of line setting 3`] = `
107+
"# test/B
108+
109+
<!-- end auto-generated rule header -->
110+
"
111+
`;
112+
113+
exports[`getEndOfLine with a ".editorconfig" file generates using the correct end of line when ".editorconfig" exists generates using the end of line from ".editorconfig" while respecting the .md specific end of line setting 4`] = `
114+
"# test/c
115+
116+
<!-- end auto-generated rule header -->
117+
"
118+
`;

0 commit comments

Comments
 (0)