diff --git a/lib/transform.js b/lib/transform.js index 205729a7..8be21676 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -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; } + // 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; + } + if (!(tocs.positions?.length > 0)) { - var insertPos = 0; + var insertPos = startPos; tocs.positions = [{ end: { range: [insertPos, insertPos], @@ -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)); diff --git a/package-lock.json b/package-lock.json index 3f9ee2a0..5bf1c074 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.0-development", "license": "MIT", "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", @@ -925,18 +925,18 @@ } }, "node_modules/@textlint/ast-node-types": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.2.tgz", - "integrity": "sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg==", + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.6.0.tgz", + "integrity": "sha512-CxZHFbYAU7J0A4izz31wV2ZZfySR6aVj2OSR6/3tppZm7VV6hM7nA7sutsLwIiBL/v4lsB1RM79l4Dc/VrH4qw==", "license": "MIT" }, "node_modules/@textlint/markdown-to-ast": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-15.5.2.tgz", - "integrity": "sha512-eLEeIb7jcyWiG1jahr3pl3z8dEYEgLPdz7lBG3AP8aB4m8Sv0SdL0mCD0tfR5hNp8FrOEXldqh1g2PMuiXlN3w==", + "version": "15.6.0", + "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-15.6.0.tgz", + "integrity": "sha512-JEuExLk5wvI3ZD0uuWiEmFKwXEpGsmESwf7SeN1xda1xFvZyeEmi1Nk53hmkHp5l1mTMC+UUxFt50EiZ30KDUQ==", "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "15.5.2", + "@textlint/ast-node-types": "15.6.0", "debug": "^4.4.3", "mdast-util-gfm-autolink-literal": "^0.1.3", "neotraverse": "^0.6.18", diff --git a/package.json b/package.json index 346532bd..14d3eacd 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/test/fixtures/readme-frontmatter-blank.md b/test/fixtures/readme-frontmatter-blank.md new file mode 100644 index 00000000..0cc3151d --- /dev/null +++ b/test/fixtures/readme-frontmatter-blank.md @@ -0,0 +1,3 @@ +--- +title: Yaml Front Matter +--- diff --git a/test/fixtures/readme-frontmatter-existing.md b/test/fixtures/readme-frontmatter-existing.md new file mode 100644 index 00000000..2060c1a3 --- /dev/null +++ b/test/fixtures/readme-frontmatter-existing.md @@ -0,0 +1,16 @@ +--- +title: Yaml Front Matter +--- + +# Title + + + +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* + +- [Title](#title) + + +## Heading + +Your regular Markdown content follows... diff --git a/test/fixtures/readme-frontmatter-invalid.md b/test/fixtures/readme-frontmatter-invalid.md new file mode 100644 index 00000000..392a80b1 --- /dev/null +++ b/test/fixtures/readme-frontmatter-invalid.md @@ -0,0 +1,7 @@ ++++ +title: Invalid Front Matter +||| + +# Heading + +Your regular Markdown content follows... diff --git a/test/fixtures/readme-frontmatter-json.md b/test/fixtures/readme-frontmatter-json.md new file mode 100644 index 00000000..b9336505 --- /dev/null +++ b/test/fixtures/readme-frontmatter-json.md @@ -0,0 +1,7 @@ +;;; +title: JSON Front Matter +;;; + +# Heading + +Your regular Markdown content follows... diff --git a/test/fixtures/readme-frontmatter-toml.md b/test/fixtures/readme-frontmatter-toml.md new file mode 100644 index 00000000..305538d7 --- /dev/null +++ b/test/fixtures/readme-frontmatter-toml.md @@ -0,0 +1,7 @@ ++++ +title: TOML Front Matter ++++ + +# Heading + +Your regular Markdown content follows... diff --git a/test/fixtures/readme-frontmatter-yaml.md b/test/fixtures/readme-frontmatter-yaml.md new file mode 100644 index 00000000..d8e82d2c --- /dev/null +++ b/test/fixtures/readme-frontmatter-yaml.md @@ -0,0 +1,7 @@ +--- +title: Yaml Front Matter +--- + +# Heading + +Your regular Markdown content follows... diff --git a/test/transform-frontmatter.js b/test/transform-frontmatter.js new file mode 100644 index 00000000..34f117cb --- /dev/null +++ b/test/transform-frontmatter.js @@ -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', + ';;; ', + '', + '', + '', + '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', + '', + '- [Heading](#heading)', + '', + '', + '', + '# 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', + '+++ ', + '', + '', + '', + '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', + '', + '- [Heading](#heading)', + '', + '', + '', + '# 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', + '---', + '', + '', + '', + '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', + '', + '- [Heading](#heading)', + '', + '', + '', + '# 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 + , [ '', + '', + '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', + '', + '- [Heading](#heading)', + '', + '', + '', + '+++', + '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', + '---', + '', + '', + '', + '', + '' ].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', + '---', + '', + '', + '', + '', + '' ].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( + res.data + , [ '---', + 'title: Yaml Front Matter', + '---', + '', + '# Title', + '', + '', + '', + '**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*', + '', + '- [Title](#title)', + ' - [Heading](#heading)', + '', + '', + '', + '## Heading', + '', + 'Your regular Markdown content follows...', + '' ].join('\n') + , 'generates correct toc for file with yaml frontmatter' + ) + + t.end() +}) diff --git a/test/transform.js b/test/transform.js index 07cb0538..b3234db9 100644 --- a/test/transform.js +++ b/test/transform.js @@ -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'); @@ -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'