Skip to content

Commit cd94000

Browse files
CloudNStoyanbmish
andauthored
Respect EOL character from .editorconfig (#590)
* add .editorconfig support for new line choice * add more tests and change the markdown path to root * add full tests inside & document the hypothetical markdown file --------- Co-authored-by: Bryan Mishkin <[email protected]>
1 parent d998fe2 commit cd94000

13 files changed

+376
-11
lines changed

lib/config-list.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { EOL } from 'node:os';
21
import {
32
BEGIN_CONFIG_LIST_MARKER,
43
END_CONFIG_LIST_MARKER,
54
} from './comment-markers.js';
65
import { markdownTable } from 'markdown-table';
76
import type { ConfigsToRules, ConfigEmojis, Plugin, Config } from './types.js';
87
import { ConfigFormat, configNameToDisplay } from './config-format.js';
9-
import { sanitizeMarkdownTable } from './string.js';
8+
import { getEndOfLine, sanitizeMarkdownTable } from './string.js';
9+
10+
const EOL = getEndOfLine();
1011

1112
/**
1213
* Check potential locations for the config description.

lib/generator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { EOL } from 'node:os';
21
import { existsSync } from 'node:fs';
32
import { dirname, join, relative, resolve } from 'node:path';
43
import { getAllNamedOptions, hasOptions } from './rule-options.js';
@@ -34,6 +33,7 @@ import { OPTION_TYPE, RuleModule } from './types.js';
3433
import { replaceRulePlaceholder } from './rule-link.js';
3534
import { updateRuleOptionsList } from './rule-options-list.js';
3635
import { mkdir, readFile, writeFile } from 'node:fs/promises';
36+
import { getEndOfLine } from './string.js';
3737

3838
function stringOrArrayWithFallback<T extends string | readonly string[]>(
3939
stringOrArray: undefined | T,
@@ -63,6 +63,8 @@ function stringOrArrayToArrayWithFallback(
6363

6464
// eslint-disable-next-line complexity
6565
export async function generate(path: string, options?: GenerateOptions) {
66+
const EOL = getEndOfLine();
67+
6668
const plugin = await loadPlugin(path);
6769
const pluginPrefix = await getPluginPrefix(path);
6870
const configsToRules = await resolveConfigsToRules(plugin);

lib/markdown.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { EOL } from 'node:os';
1+
import { getEndOfLine } from './string.js';
22

33
// General helpers for dealing with markdown files / content.
44

@@ -14,6 +14,8 @@ export function replaceOrCreateHeader(
1414
newHeader: string,
1515
marker: string,
1616
) {
17+
const EOL = getEndOfLine();
18+
1719
const lines = markdown.split(EOL);
1820

1921
const titleLineIndex = lines.findIndex((line) => line.startsWith('# '));
@@ -45,6 +47,8 @@ export function findSectionHeader(
4547
markdown: string,
4648
str: string,
4749
): string | undefined {
50+
const EOL = getEndOfLine();
51+
4852
// Get all the matching strings.
4953
const regexp = new RegExp(`## .*${str}.*${EOL}`, 'giu');
5054
const sectionPotentialMatches = [...markdown.matchAll(regexp)].map(
@@ -68,6 +72,8 @@ export function findSectionHeader(
6872
}
6973

7074
export function findFinalHeaderLevel(str: string) {
75+
const EOL = getEndOfLine();
76+
7177
const lines = str.split(EOL);
7278
const finalHeader = lines.reverse().find((line) => line.match('^(#+) .+$'));
7379
return finalHeader ? finalHeader.indexOf(' ') : undefined;

lib/rule-doc-notices.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { EOL } from 'node:os';
21
import { END_RULE_HEADER_MARKER } from './comment-markers.js';
32
import {
43
EMOJI_DEPRECATED,
@@ -27,9 +26,12 @@ import {
2726
toSentenceCase,
2827
removeTrailingPeriod,
2928
addTrailingPeriod,
29+
getEndOfLine,
3030
} from './string.js';
3131
import { ConfigFormat, configNameToDisplay } from './config-format.js';
3232

33+
const EOL = getEndOfLine();
34+
3335
function severityToTerminology(severity: SEVERITY_TYPE) {
3436
switch (severity) {
3537
case SEVERITY_TYPE.error: {

lib/rule-list-legend.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { EOL } from 'node:os';
21
import {
32
EMOJI_DEPRECATED,
43
EMOJI_FIXABLE,
@@ -18,6 +17,9 @@ import {
1817
} from './types.js';
1918
import { RULE_TYPE_MESSAGES_LEGEND, RULE_TYPES } from './rule-type.js';
2019
import { ConfigFormat, configNameToDisplay } from './config-format.js';
20+
import { getEndOfLine } from './string.js';
21+
22+
const EOL = getEndOfLine();
2123

2224
export const SEVERITY_TYPE_TO_WORD: {
2325
[key in SEVERITY_TYPE]: string;

lib/rule-list.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { EOL } from 'node:os';
21
import {
32
BEGIN_RULE_LIST_MARKER,
43
END_RULE_LIST_MARKER,
@@ -34,7 +33,11 @@ import type {
3433
import { EMOJIS_TYPE } from './rule-type.js';
3534
import { hasOptions } from './rule-options.js';
3635
import { getLinkToRule } from './rule-link.js';
37-
import { capitalizeOnlyFirstLetter, sanitizeMarkdownTable } from './string.js';
36+
import {
37+
capitalizeOnlyFirstLetter,
38+
getEndOfLine,
39+
sanitizeMarkdownTable,
40+
} from './string.js';
3841
import { noCase } from 'change-case';
3942
import { getProperty } from 'dot-prop';
4043
import { boolean, isBooleanable } from 'boolean';
@@ -269,6 +272,8 @@ function generateRuleListMarkdownForRulesAndHeaders(
269272
ignoreConfig: readonly string[],
270273
urlRuleDoc?: string | UrlRuleDocFunction,
271274
): string {
275+
const EOL = getEndOfLine();
276+
272277
const parts: string[] = [];
273278

274279
for (const { title, rules } of rulesAndHeaders) {
@@ -416,6 +421,8 @@ export function updateRulesList(
416421
urlConfigs?: string,
417422
urlRuleDoc?: string | UrlRuleDocFunction,
418423
): string {
424+
const EOL = getEndOfLine();
425+
419426
let listStartIndex = markdown.indexOf(BEGIN_RULE_LIST_MARKER);
420427
let listEndIndex = markdown.indexOf(END_RULE_LIST_MARKER);
421428

lib/rule-options-list.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { EOL } from 'node:os';
21
import {
32
BEGIN_RULE_OPTIONS_LIST_MARKER,
43
END_RULE_OPTIONS_LIST_MARKER,
54
} from './comment-markers.js';
65
import { markdownTable } from 'markdown-table';
76
import type { RuleModule } from './types.js';
87
import { RuleOption, getAllNamedOptions } from './rule-options.js';
9-
import { sanitizeMarkdownTable } from './string.js';
8+
import { getEndOfLine, sanitizeMarkdownTable } from './string.js';
9+
10+
const EOL = getEndOfLine();
1011

1112
export enum COLUMN_TYPE {
1213
// Alphabetical order.

lib/string.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { EOL } from 'node:os';
2+
import editorconfig from 'editorconfig';
3+
4+
const endOfLine = getEndOfLine();
25

36
export function toSentenceCase(str: string) {
47
return str.replace(/^\w/u, function (txt) {
@@ -24,11 +27,30 @@ export function capitalizeOnlyFirstLetter(str: string) {
2427
function sanitizeMarkdownTableCell(text: string): string {
2528
return text
2629
.replaceAll('|', String.raw`\|`)
27-
.replaceAll(new RegExp(EOL, 'gu'), '<br/>');
30+
.replaceAll(new RegExp(endOfLine, 'gu'), '<br/>');
2831
}
2932

3033
export function sanitizeMarkdownTable(
3134
text: readonly (readonly string[])[],
3235
): readonly (readonly string[])[] {
3336
return text.map((row) => row.map((col) => sanitizeMarkdownTableCell(col)));
3437
}
38+
39+
// Gets the end of line string while respecting the
40+
// `.editorconfig` and falling back to `EOL` from `node:os`.
41+
export function getEndOfLine() {
42+
// The passed `markdown.md` argument is used as an example
43+
// of a markdown file in the plugin root folder in order to
44+
// check for any specific markdown configurations.
45+
const config = editorconfig.parseSync('markdown.md');
46+
47+
let endOfLine = EOL;
48+
49+
if (config.end_of_line === 'lf') {
50+
endOfLine = '\n';
51+
} else if (config.end_of_line === 'crlf') {
52+
endOfLine = '\r\n';
53+
}
54+
55+
return endOfLine;
56+
}

package-lock.json

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"cosmiconfig": "^9.0.0",
5454
"deepmerge": "^4.2.2",
5555
"dot-prop": "^9.0.0",
56+
"editorconfig": "^2.0.0",
5657
"jest-diff": "^29.2.1",
5758
"json-schema": "^0.4.0",
5859
"json-schema-traverse": "^1.0.0",
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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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[`string (getEndOfLine) 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)