Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d844081
Scaffold #279
thompson-tomo Apr 16, 2026
599d5c2
Add tests for front matter
thompson-tomo Apr 17, 2026
330c033
add new line at the end
thompson-tomo Apr 17, 2026
0feafb1
Add safeguards for not enough content.
thompson-tomo Apr 19, 2026
eec04c4
Update transform.js
thompson-tomo Apr 19, 2026
4eefc39
Update transform.js
thompson-tomo Apr 19, 2026
a79acc1
Update transform.js
thompson-tomo Apr 19, 2026
e43be0a
Add more tests
thompson-tomo Apr 19, 2026
c6dff5c
update test fixtures
thompson-tomo Apr 19, 2026
f4ae29a
fix filename
thompson-tomo Apr 19, 2026
774e0ea
test fix
thompson-tomo Apr 19, 2026
3549694
fix all test
thompson-tomo Apr 19, 2026
811c26a
Update readme-frontmatter-existing.md
thompson-tomo Apr 19, 2026
8e2da05
Update transform.js
thompson-tomo Apr 19, 2026
2583873
Update transform.js
thompson-tomo Apr 19, 2026
655a610
Update transform.js
thompson-tomo Apr 19, 2026
ee25bb1
Update transform.js
thompson-tomo Apr 19, 2026
343fb32
Update transform.js
thompson-tomo Apr 19, 2026
5a1d07b
Merge branch 'thlorenz:master' into feat/#279_frontMatter
thompson-tomo Apr 19, 2026
47313e8
Update transform.js
thompson-tomo Apr 19, 2026
f54826e
Update transform.js
thompson-tomo Apr 21, 2026
6f2cff9
Update transform.js
thompson-tomo Apr 21, 2026
408b826
Update transform.js
thompson-tomo Apr 21, 2026
ba80026
Merge branch 'thlorenz:master' into feat/#279_frontMatter
thompson-tomo Apr 22, 2026
c9d6e77
Use defined eol character
thompson-tomo Apr 22, 2026
49b8b3f
Merge branch 'master' into feat/#279_frontMatter
thompson-tomo Apr 25, 2026
fba8690
Merge branch 'master' into feat/#279_frontMatter
thompson-tomo Apr 25, 2026
ce64fbb
Improve test to cater for TOC not always first
thompson-tomo Apr 27, 2026
e9e8b17
Bump markdown parser
thompson-tomo Apr 27, 2026
a5e8498
Merge branch 'master' into feat/#279_frontMatter
thompson-tomo May 5, 2026
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
30 changes: 19 additions & 11 deletions lib/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,29 +207,37 @@ exports = module.exports = function transform(content, mode, maxHeaderLevel, min
if (!(tocs.positions?.length > 0) && updateOnly) {
return { transformed: false };
}

var startLine = 0;
var frontMatter = false;
var frontMatterDelimiters = ['+++', ';;;'];
var startPos = 0;
var firstChild = docNodes.children.length > 0 ? docNodes.children[0] : undefined;
if (firstChild?.type === 'Yaml') {
frontMatter = true;
} else if (firstChild?.type === 'Paragraph') {
frontMatter = frontMatterDelimiters.some(d =>
firstChild.raw.startsWith(d) && firstChild.raw.trim().endsWith(d));
}
var frontMatter = firstChild?.type === 'Json' || firstChild?.type === 'Toml' || firstChild?.type === 'Yaml';

var hasBody = docNodes.children.length > (frontMatter ? 1 : 0);
if (frontMatter && hasBody) {
startLine = docNodes.children[1].loc.start.line - 1;
startPos = docNodes.children[1].range[0];
}
// force it on to the next line after current content
else if (frontMatter) {
startLine = docNodes.children[0].loc.end.line;
startPos = docNodes.range[1] + 1;
}

// adds in the empty line between end of content & new toc if missing
if (frontMatter && docNodes.range[1] === docNodes.children[0].range[1]) {
startPos = startPos + eol.length;
content += eol;
Comment thread
thompson-tomo marked this conversation as resolved.
}

// if there is no empty line between front matter & content add one
if (frontMatter && hasBody && docNodes.children[0].loc.end.line + 1 == docNodes.children[1].loc.start.line) {
content = content.substring(0, startPos) + eol + content.substring(startPos);
startPos = startPos + eol.length;
}
Comment thread
thompson-tomo marked this conversation as resolved.

if (!(tocs.positions?.length > 0)) {
var insertPos = 0;
var insertPos = startPos;
tocs.positions = [{
end: {
range: [insertPos, insertPos],
Expand All @@ -244,7 +252,7 @@ exports = module.exports = function transform(content, mode, maxHeaderLevel, min
}

var tocPosition = tocs.positions[0];
var tocableStart = !processAll ? tocPosition.end.range[1] : 0;
var tocableStart = !processAll ? tocPosition.end.range[1] : startPos;
var headers = getMarkdownHeaders(docNodes, tocableStart, maxHeaderLevel, minHeaderLevel)
.concat(getHtmlHeaders(docNodes, tocableStart, maxHeaderLevelHtml, minHeaderLevel));

Expand Down
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"main": "doctoc.js",
"bin": "doctoc.js",
"dependencies": {
"@textlint/markdown-to-ast": "^15.5.2",
"@textlint/markdown-to-ast": "^15.6.0",
"anchor-markdown-header": "^0.8.4",
"htmlparser2": "^7.2.0",
"loglevel": "^1.9.2",
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/readme-frontmatter-blank.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
title: Yaml Front Matter
---
16 changes: 16 additions & 0 deletions test/fixtures/readme-frontmatter-existing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
---
title: Yaml Front Matter
---

# Title

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*

- [Title](#title)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Heading

Your regular Markdown content follows...
7 changes: 7 additions & 0 deletions test/fixtures/readme-frontmatter-invalid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
+++
title: Invalid Front Matter
|||

# Heading

Your regular Markdown content follows...
7 changes: 7 additions & 0 deletions test/fixtures/readme-frontmatter-json.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
;;;
title: JSON Front Matter
;;;

# Heading

Your regular Markdown content follows...
7 changes: 7 additions & 0 deletions test/fixtures/readme-frontmatter-toml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
+++
title: TOML Front Matter
+++

# Heading

Your regular Markdown content follows...
7 changes: 7 additions & 0 deletions test/fixtures/readme-frontmatter-yaml.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Yaml Front Matter
---

# Heading

Your regular Markdown content follows...
188 changes: 188 additions & 0 deletions test/transform-frontmatter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
'use strict';
/*jshint asi: true */

var test = require('tap').test
, transform = require('../lib/transform')

test('\ngiven a file that includes Json frontmatter', function (t) {
var content = require('fs').readFileSync(__dirname + '/fixtures/readme-frontmatter-json.md', 'utf8');
var res = transform(content);

t.same(
res.data
, [ ';;;',
'title: JSON Front Matter',
';;; ',
'',
'<!-- START doctoc generated TOC please keep comment here to allow auto update -->',
'<!-- DON\'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->',
'**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*',
'',
'- [Heading](#heading)',
'',
'<!-- END doctoc generated TOC please keep comment here to allow auto update -->',
'',
'# Heading',
'',
'Your regular Markdown content follows...',
'' ].join('\n')
, 'generates correct toc for file with Json frontmatter'
)

t.end()
})

test('\ngiven a file that includes Toml frontmatter', function (t) {
var content = require('fs').readFileSync(__dirname + '/fixtures/readme-frontmatter-toml.md', 'utf8');
var res = transform(content);

t.same(
res.data
, [ '+++',
'title: TOML Front Matter',
'+++ ',
'',
'<!-- START doctoc generated TOC please keep comment here to allow auto update -->',
'<!-- DON\'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->',
'**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*',
'',
'- [Heading](#heading)',
'',
'<!-- END doctoc generated TOC please keep comment here to allow auto update -->',
'',
'# Heading',
'',
'Your regular Markdown content follows...',
'' ].join('\n')
, 'generates correct toc for file with Toml frontmatter'
)

t.end()
})

test('\ngiven a file that includes yaml frontmatter', function (t) {
var content = require('fs').readFileSync(__dirname + '/fixtures/readme-frontmatter-yaml.md', 'utf8');
var res = transform(content);

t.same(
res.data
, [ '---',
'title: Yaml Front Matter',
'---',
'',
'<!-- START doctoc generated TOC please keep comment here to allow auto update -->',
'<!-- DON\'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->',
'**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*',
'',
'- [Heading](#heading)',
'',
'<!-- END doctoc generated TOC please keep comment here to allow auto update -->',
'',
'# Heading',
'',
'Your regular Markdown content follows...',
'' ].join('\n')
, 'generates correct toc for file with yaml frontmatter'
)

t.end()
})

test('\ngiven a file that includes invalid frontmatter', function (t) {
var content = require('fs').readFileSync(__dirname + '/fixtures/readme-frontmatter-invalid.md', 'utf8');
var res = transform(content);

t.same(
res.data
, [ '<!-- START doctoc generated TOC please keep comment here to allow auto update -->',
'<!-- DON\'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->',
'**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*',
'',
'- [Heading](#heading)',
'',
'<!-- END doctoc generated TOC please keep comment here to allow auto update -->',
'',
'+++',
'title: Invalid Front Matter',
'|||',
'',
'# Heading',
'',
'Your regular Markdown content follows...',
'' ].join('\n')
, 'generates correct toc for file with invalid frontmatter'
)

t.end()
})

test('\ngiven a file that has only yaml frontmatter and empty line', function (t) {
var content = require('fs').readFileSync(__dirname + '/fixtures/readme-frontmatter-blank.md', 'utf8');
var res = transform(content.trim() + '\n');

t.same(
res.data
, [ '---',
'title: Yaml Front Matter',
'---',
'',
'<!-- START doctoc generated TOC please keep comment here to allow auto update -->',
'<!-- DON\'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->',
'<!-- END doctoc generated TOC please keep comment here to allow auto update -->',
'' ].join('\n')
, 'generates correct toc for file with only yaml frontmatter'
)

t.end()
})

test('\ngiven a file that has only yaml frontmatter and no trailing whitespace', function (t) {
var content = require('fs').readFileSync(__dirname + '/fixtures/readme-frontmatter-blank.md', 'utf8');
var res = transform(content.trim());

t.same(
res.data
, [ '---',
'title: Yaml Front Matter',
'---',
'',
'<!-- START doctoc generated TOC please keep comment here to allow auto update -->',
'<!-- DON\'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->',
'<!-- END doctoc generated TOC please keep comment here to allow auto update -->',
'' ].join('\n')
, 'generates correct toc for file with only yaml frontmatter'
)

t.end()
})

test('\ngiven a file that includes yaml frontmatter and process all', function (t) {
var content = require('fs').readFileSync(__dirname + '/fixtures/readme-frontmatter-existing.md', 'utf8');
var res = transform(content, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true);

t.same(
Comment thread
thompson-tomo marked this conversation as resolved.
res.data
, [ '---',
'title: Yaml Front Matter',
'---',
'',
'# Title',
'',
'<!-- START doctoc generated TOC please keep comment here to allow auto update -->',
'<!-- DON\'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->',
'**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*',
'',
'- [Title](#title)',
' - [Heading](#heading)',
'',
'<!-- END doctoc generated TOC please keep comment here to allow auto update -->',
'',
'## Heading',
'',
'Your regular Markdown content follows...',
'' ].join('\n')
, 'generates correct toc for file with yaml frontmatter'
)

t.end()
})
38 changes: 37 additions & 1 deletion test/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,23 @@ function check(md, anchors, mode, maxHeaderLevel, minHeaderLevel, minTocItems, t
// build the expected content
eol = eol || '\n';
var pragma = contentGenerator.pragmaMarkers(syntax || 'md');
var body = '';
var doc = '';
var tag = md.substring(0, 3);
const tags = ['---', ';;;', '+++'];
if (tags.includes(tag) && md.indexOf(tag, 3) > 0) {
var pos = md.indexOf(tag, 3) + 3 + eol.length; // find the closing tag and skip that line
body = md.substring(pos);
if (body.startsWith(eol)) { body = body.substring(eol.length); }
doc = md.substring(0, pos) + eol;
}
else {
body = md;
}
var contents = anchors.trimEnd();
if (contents != '') { contents += eol; }
var toc = pragma.start + eol + anchors.trimEnd() + (anchors ? eol + eol : '') + pragma.end;
var doc = res.wrappedToc + eol + eol + md;
doc = doc + res.wrappedToc + eol + eol + body;

t.ok(res.transformed, 'transforms it');
t.same(res.toc, contents, 'generates correct toc contents');
Expand Down Expand Up @@ -1065,6 +1078,29 @@ check(
, 'mdx'
)

check(
[ '---'
, ''
, '# Real Heading'
, 'body'
].join('\n')
, [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n'
, '- [Real Heading](#real-heading)\n\n\n'
].join('')
)

check(
[ '---'
, 'title: Yaml'
, '---'
, '# H'
, 'body'
].join('\n')
, [ '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n'
, '- [H](#h)\n\n\n'
].join('')
)

test('should use <!-- --> comments if syntax=md', function (t) {
var res = transform(
[ '# My Module'
Expand Down