Skip to content

Commit 594186c

Browse files
committed
added logic in trailing spaces so we do not remove the space between the list item and the content which breaks list items
1 parent 98e129a commit 594186c

File tree

3 files changed

+125
-26
lines changed

3 files changed

+125
-26
lines changed

__tests__/trailing-spaces.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,5 +98,61 @@ ruleTest({
9898
[[File with spaces]]
9999
`,
100100
},
101+
{ // accounts for https://github.com/platers/obsidian-linter/issues/868
102+
testName: 'A list item with no content should not have the space indicating it is a list item removed',
103+
before: dedent`
104+
- List item 1
105+
-${' '}
106+
-${' '}
107+
`,
108+
after: dedent`
109+
- List item 1
110+
-${' '}
111+
-${' '}
112+
`,
113+
},
114+
{ // relates to for https://github.com/platers/obsidian-linter/issues/868
115+
testName: 'Make sure that checklists are properly handled with trailing spaces',
116+
before: dedent`
117+
- [ ] List item 1
118+
- [ ]${' '}
119+
- [ ] ${' '}
120+
`,
121+
after: dedent`
122+
- [ ] List item 1
123+
- [ ]${' '}
124+
- [ ]${' '}
125+
`,
126+
},
127+
{ // relates to for https://github.com/platers/obsidian-linter/issues/868
128+
testName: 'Make sure that indented lists are properly handled with trailing spaces',
129+
before: dedent`
130+
Text here
131+
- List item 1
132+
-${' '}
133+
-${' '}
134+
`,
135+
after: dedent`
136+
Text here
137+
- List item 1
138+
-${' '}
139+
-${' '}
140+
`,
141+
},
142+
{ // relates to for https://github.com/platers/obsidian-linter/issues/868
143+
testName: 'Make sure that ordered lists are properly handled with trailing spaces',
144+
before: dedent`
145+
Text here
146+
1. List item 1
147+
2.${' '}
148+
3.${' '}
149+
`,
150+
after: dedent`
151+
Text here
152+
1. List item 1
153+
2.${' '}
154+
3.${' '}
155+
`,
156+
},
101157
],
102158
});

src/rules/trailing-spaces.ts

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import {IgnoreTypes} from '../utils/ignore-types';
1+
import {ignoreListOfTypes, IgnoreTypes} from '../utils/ignore-types';
22
import {Options, RuleType} from '../rules';
33
import RuleBuilder, {BooleanOptionBuilder, ExampleBuilder, OptionBuilderBase} from './rule-builder';
44
import dedent from 'ts-dedent';
5+
import {updateListItemText} from '../utils/mdast';
56

67
class TrailingSpacesOptions implements Options {
78
twoSpaceLineBreak: boolean = false;
@@ -22,14 +23,27 @@ export default class TrailingSpaces extends RuleBuilder<TrailingSpacesOptions> {
2223
return TrailingSpacesOptions;
2324
}
2425
apply(text: string, options: TrailingSpacesOptions): string {
25-
if (!options.twoSpaceLineBreak) {
26-
return text.replace(/[ \t]+$/gm, '');
27-
} else {
28-
text = text.replace(/(\S)[ \t]$/gm, '$1'); // one whitespace
29-
text = text.replace(/(\S)[ \t]{3,}$/gm, '$1'); // three or more whitespaces
30-
text = text.replace(/(\S)( ?\t\t? ?)$/gm, '$1'); // two whitespaces with at least one tab
31-
return text;
32-
}
26+
text = ignoreListOfTypes([IgnoreTypes.list], text, (text: string): string => {
27+
if (!options.twoSpaceLineBreak) {
28+
return text.replace(/[ \t]+$/gm, '');
29+
} else {
30+
text = text.replace(/(\S)[ \t]$/gm, '$1'); // one whitespace
31+
text = text.replace(/(\S)[ \t]{3,}$/gm, '$1'); // three or more whitespaces
32+
text = text.replace(/(\S)( ?\t\t? ?)$/gm, '$1'); // two whitespaces with at least one tab
33+
return text;
34+
}
35+
});
36+
37+
return updateListItemText(text, (text: string): string => {
38+
if (!options.twoSpaceLineBreak) {
39+
return text.replace(/[ \t]+$/gm, '');
40+
} else {
41+
text = text.replace(/(\S)[ \t]$/gm, '$1'); // one whitespace
42+
text = text.replace(/(\S)[ \t]{3,}$/gm, '$1'); // three or more whitespaces
43+
text = text.replace(/(\S)( ?\t\t? ?)$/gm, '$1'); // two whitespaces with at least one tab
44+
return text;
45+
}
46+
}, true);
3347
}
3448
get exampleBuilders(): ExampleBuilder<TrailingSpacesOptions>[] {
3549
return [

src/utils/mdast.ts

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ import {getTextInLanguage} from '../lang/helpers';
1919

2020
const LRU = new QuickLRU({maxSize: 200});
2121

22+
type PositionPlusEmptyIndicator = {
23+
position: Position,
24+
isEmpty: boolean,
25+
}
26+
2227
export enum MDAstTypes {
2328
Link = 'link',
2429
Footnote = 'footnoteDefinition',
@@ -107,27 +112,40 @@ export function getPositions(type: MDAstTypes, text: string): Position[] {
107112
/**
108113
* Gets the positions of the list item text in the given text.
109114
* @param {string} text - The markdown text
110-
* @return {Position[]} The positions of the list item text in the given text
115+
* @param {boolean} includeEmptyNodes - Whether or not empty list items should be
116+
* returned to be handled by the calling function
117+
* @return {PositionPlusEmptyIndicator[]} The positions of the list item text in the given text
118+
* with a status as to whether or not they are empty
111119
*/
112-
function getListItemTextPositions(text: string): Position[] {
120+
function getListItemTextPositions(text: string, includeEmptyNodes: boolean = false): Position[] {
113121
const ast = parseTextToAST(text);
114-
const positions: Position[] = [];
122+
const positions: PositionPlusEmptyIndicator[] = [];
115123
visit(ast, MDAstTypes.ListItem as string, (node) => {
116124
// @ts-ignore the fact that not all nodes have a children property since I am skipping any that do not
117-
if (!node.children) {
125+
if (!node.children || node.children.length === 0) {
126+
if (includeEmptyNodes) {
127+
positions.push({
128+
position: node.position,
129+
isEmpty: true,
130+
});
131+
}
132+
118133
return;
119134
}
120135

121136
// @ts-ignore the fact that not all nodes have a children property since I have already exited the function if that is the case
122137
for (const childNode of node.children) {
123138
if (childNode.type === (MDAstTypes.Paragraph as string)) {
124-
positions.push(childNode.position);
139+
positions.push({
140+
position: childNode.position,
141+
isEmpty: false,
142+
});
125143
}
126144
}
127145
});
128146

129147
// Sort positions by start position in reverse order
130-
positions.sort((a, b) => b.start.offset - a.start.offset);
148+
positions.sort((a, b) => b.position.start.offset - a.position.start.offset);
131149
return positions;
132150
}
133151

@@ -587,29 +605,40 @@ export function updateBoldText(text: string, func:(text: string) => string): str
587605
return text;
588606
}
589607

590-
export function updateListItemText(text: string, func:(text: string) => string): string {
591-
const positions: Position[] = getListItemTextPositions(text);
608+
export function updateListItemText(text: string, func:(text: string) => string, includeEmptyNodes: boolean = false): string {
609+
const positions: PositionPlusEmptyIndicator[] = getListItemTextPositions(text, includeEmptyNodes);
592610

593611
for (const position of positions) {
594-
let startIndex = position.start.offset;
595-
// get the actual start of the list item leaving only 1 whitespace between the indicator and the text
596-
while (startIndex > 0 && text.charAt(startIndex - 1).trim() === '') {
597-
startIndex--;
598-
}
599-
// keep a single space for the indicator
600-
if (startIndex === 0 || text.charAt(startIndex - 1).trim() != '') {
612+
let startIndex = position.position.start.offset;
613+
if (position.isEmpty) {
614+
// get the actual start of the list item leaving only 1 whitespace between the indicator and the text
615+
while (startIndex < position.position.end.offset && text.charAt(startIndex).trim() !== '') {
616+
startIndex++;
617+
}
618+
601619
startIndex++;
620+
} else {
621+
// get the actual start of the list item leaving only 1 whitespace between the indicator and the text
622+
while (startIndex > 0 && text.charAt(startIndex - 1).trim() === '') {
623+
startIndex--;
624+
}
625+
626+
// keep a single space for the indicator
627+
if (startIndex === 0 || text.charAt(startIndex - 1).trim() != '') {
628+
startIndex++;
629+
}
602630
}
603631

604-
let listText = text.substring(startIndex, position.end.offset);
632+
let listText = text.substring(startIndex, position.position.end.offset);
605633
// for some reason some checklists are not getting treated as such and this causes the task indicator to be included in the text
606634
if (checklistBoxStartsTextRegex.test(listText)) {
607635
startIndex += 4;
608636
listText = listText.substring(4);
609637
}
638+
610639
listText = func(listText);
611640

612-
text = replaceTextBetweenStartAndEndWithNewValue(text, startIndex, position.end.offset, listText);
641+
text = replaceTextBetweenStartAndEndWithNewValue(text, startIndex, position.position.end.offset, listText);
613642
}
614643

615644
return text;

0 commit comments

Comments
 (0)