Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions src/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@ const isTerm = (word: string, line: string) => {
});
};

const indexCodeSpans = (line: string) => {
const spans = new Map<number, number>();
const nextEnds = new Map<number, number>();

// Index the nearest closing run of each length without rescanning the suffix.
for (let end = line.length; end > 0;) {
if (line[end - 1] !== '`') {
end--;
continue;
}
let start = end - 1;
while (start > 0 && line[start - 1] === '`') {
start--;
}

const length = end - start;
spans.set(start, nextEnds.get(length) ?? end);
// An escaped first backtick leaves the rest of the run as a possible opener.
if (length > 1) {
spans.set(start + 1, nextEnds.get(length - 1) ?? end);
}
nextEnds.set(length, end);
end = start;
}

return spans;
};

const lineToWords = (line: string) => {
const words: WordMeta[] = [];

Expand All @@ -25,8 +53,18 @@ const lineToWords = (line: string) => {
value: '',
};

for (const char of line.split('')) {
if (/\s/.test(char)) {
const codeSpans = indexCodeSpans(line);

for (let index = 0; index < line.length; index++) {
let char = line[index];
if (char === '\\' && /[\\`]/.test(line[index + 1] ?? '')) {
char += line[++index];
} else if (char === '`') {
const end = codeSpans.get(index)!;
char = line.slice(index, end);
index = end - 1;
}
if (/^\s$/.test(char)) {
if (lastWord.type === 'space') {
lastWord.value += char;
} else {
Expand Down
20 changes: 20 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ test('should format line as expected', () => {
);
});

test('should preserve inline code while formatting heading text', () => {
const cases = [
['## Use `Foo Bar Baz` Component', '## Use `Foo Bar Baz` component'],
[
'## Use ``Foo `Bar` Baz`` Component',
'## Use ``Foo `Bar` Baz`` component',
],
['## Use `Foo Bar Component', '## Use `Foo bar component'],
[
'## Use \\`Foo Bar Baz\\` Component',
'## Use \\`Foo bar Baz\\` component',
],
['## Use \\``Foo Bar Baz` Component', '## Use \\``Foo Bar Baz` component'],
];

for (const [input, expected] of cases) {
assert.strictEqual(formatLine(input), expected);
}
});

test('should preserve the existing CLI check and write usage', async () => {
const cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'heading-case-cli-'));
const filePath = path.join(cwd, 'guide.md');
Expand Down