Skip to content

Commit

Permalink
Update algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
ilyatitovich committed Apr 15, 2024
1 parent 264cee4 commit 374f11f
Show file tree
Hide file tree
Showing 14 changed files with 443 additions and 101 deletions.
122 changes: 70 additions & 52 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,90 @@
/*
import { lintRule } from 'unified-lint-rule'
import findTOC from './lib/findTOC.js'
import createListOfSections from './lib/createListOfSections.js'
import getTOCSections from './lib/getTOCSections.js'

1. Find TOC:
- Look for list items (listItem) that contain links (link) with URLs pointing to headings within the document (usually identified by URLs starting with #).
function checkTOC(tree, file) {
const toc = findTOC(tree)

2. Collect all headings from document.
// If TOC not detected - stop plugin
if (!toc) {
file.data.customInfo = {
message: 'No TOC detected'
}

3. Find TOC elemets and compare them with the existing titles.
return false
}

*/
// Create a list of sections from documents that represent TOC
const docSections = createListOfSections(tree)
const docSectionsLength = docSections.length

import { lintRule } from 'unified-lint-rule'
import { visit } from 'unist-util-visit'
// Collect TOC headings in object for quick checking
const tocSections = getTOCSections(toc)

function checkToc(tree, file) {
// Find out if the document contains a TOC
let tocIsExist = false
visit(tree, 'listItem', node => {
if (node.children[0].type === 'paragraph') {
// Try to extract URL
const { url } = node.children[0].children[0]

// If so - check is it TOC URL or not
if (url && url.startsWith('#')) {
tocIsExist = true
return
}
}
})
// Check if forgot to update TOC when sections was deleted
if (tocSections.list.length > docSectionsLength) {
file.message(
`TOС contains more sections than document`,
toc // TOC position
)
}

// If the TOC is not detected, no further calculations are performed
if (!tocIsExist) {
file.data.customInfo = {
message: 'No TOC detected. Stopping the plugin.'
}
// Check correspondence of document sections to the existing TOC
for (let i = 0; i < docSectionsLength; i++) {
const { title, url, depth } = docSections[i]

return
}
// Check that the document section is included in the table of contents.
if (!tocSections[title]) {
file.message(
`Section: '${title}' was not found in TOС`,
toc // TOC position
)

// Collect all headings and add right URLs
const headings = {}
continue // If not - check next section
}

visit(tree, 'heading', node => {
const heading = node.children[0].value
headings[heading] = {
url:
'#' +
heading
.toLowerCase()
.replace(/\s+/g, '-')
.replace(/[^\w-]+/g, '')
// Check that URl is correct
if (url !== tocSections[title].url) {
file.message(
`Incorrect URL in TOС: '${tocSections[title].url}'`,
tocSections[title].position // position node with url in TOC
)
}
})

// Find TOC elemets and compare them with the existing titles
visit(tree, 'link', node => {
if (node.url.startsWith('#')) {
const tocTitle = node.children[0].value
// Check the order of sections in TOС
if (title !== tocSections.list[i]) {
// Find index of the section in existing TOC
const sectionIndex = tocSections.list.indexOf(title)

if (!headings[tocTitle] || node.url !== headings[tocTitle].url) {
if (i === 0) {
file.message(
`Incorrect title or URL in TOC. Title: ${tocTitle}, URL: ${node.url}`,
node
`Incorrect order in TOC: '${title}' must be first element`,
tocSections[title].position
)
} // Check that the sections are in the same order as in the document
else if (
docSections[i - 1].title !== tocSections.list[sectionIndex - 1]
) {
file.message(
`Incorrect order in TOC: '${title}' must be after '${
docSections[i - 1].title
}'`,
tocSections[title].position
)
}
}
})

// Check the section hierarchy according to the heading levels in the document
if (depth !== tocSections[title].depth) {
file.message(
`Incorrect section hierarchy in TOC: '${title}' `,
tocSections[title].position // position node with url in TOC
)
}
}
}

const remarkLintCheckToc = lintRule('remark-lint:check-toc', checkToc)
const remarkLintCheckTOC = lintRule('remark-lint:check-toc', checkTOC)

export default remarkLintCheckToc
export default remarkLintCheckTOC
20 changes: 20 additions & 0 deletions lib/createListOfSections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { visit } from "unist-util-visit";
import { slug } from "github-slugger";

export default function createListOfSections(tree) {
const docSections = []; // Use array for represent order of sections in doc

visit(tree, "heading", (node) => {
if (node.depth > 1) {
const title = node.children[0].value;
docSections.push({
depth: node.depth - 1, // Heading hierarchy level in TOC (## = 1, or ### = 2 etc.)
title: title,
url: "#" + slug(title),
position: node.position,
});
}
});

return docSections;
}
26 changes: 26 additions & 0 deletions lib/findTOC.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { find } from "unist-util-find";

export default function findTOC(tree) {
return find(tree, (node) => {
return (
node.type === "list" &&
node.children.some((listItem) => {
return (
listItem.type === "listItem" &&
listItem.children.some((paragraph) => {
return (
paragraph.type === "paragraph" &&
paragraph.children.some((linkNode) => {
return (
linkNode.type === "link" &&
linkNode.url &&
linkNode.url.startsWith("#")
);
})
);
})
);
})
);
});
}
18 changes: 18 additions & 0 deletions lib/getTOCSections.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { visitParents } from "unist-util-visit-parents";

export default function getTOCSections(tocList) {
const tocSections = { list: [] }; // list array that represent order of headings in TOC
visitParents(tocList, "link", (node, ancestors) => {
const title = node.children[0].value;
tocSections[title] = {
depth: ancestors.filter((ancestor) => ancestor.type === "listItem")
.length, // Get the heading hierarchy level
url: node.url,
position: node.position,
};

tocSections.list.push(title);
});

return tocSections;
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,10 @@
"cleanDocs": true
},
"dependencies": {
"github-slugger": "^2.0.0",
"unified-lint-rule": "^2.1.2",
"unist-util-visit": "^5.0.0"
"unist-util-find": "^3.0.0",
"unist-util-visit": "^5.0.0",
"unist-util-visit-parents": "^6.0.1"
}
}
25 changes: 25 additions & 0 deletions pnpm-lock.yaml

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

38 changes: 38 additions & 0 deletions test/docs/incorrect-hierarchy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# My Doc with TOC

***

* [Heading One](#heading-one)
* [Heading Two](#heading-two)
* [Sub Heading One](#sub-heading-one)
* [Sub Heading Three](#sub-heading-three)
* [Heading Three](#heading-three)
* [Heading Four](#heading-four)

## Heading One

Lorem ipsum dolor sit amet.

## Heading Two

Lorem ipsum dolor sit amet.

### Sub Heading One

Lorem ipsum dolor sit amet.

### Sub Heading Three

Lorem ipsum dolor sit amet.

## Heading Three

Lorem ipsum dolor sit amet.

## Heading Four

* Item 1
* Item 2
* Sub Item 1
* [Sub Item Link](https://www.google.com/)
* `code block`
11 changes: 6 additions & 5 deletions test/docs/invalid-toc.md → test/docs/incorrect-order.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

***

* [Heading One](#heading-one)
* [Heading Two](#heading-two)
* [Sub Heding One](#sub-heading-one)
* [Sub Heading Three](#subheading-three)
* [Heading Three](#heading)
* [Heading One](#heading-one)
* [Sub Heading One](#sub-heading-one)
* [Sub Heading Three](#sub-heading-three)
* [Heading Four](#heading-four)
* [Heading Three](#heading-three)

## Heading One

Expand All @@ -28,7 +29,7 @@ Lorem ipsum dolor sit amet.

Lorem ipsum dolor sit amet.

### Some List
## Heading Four

* Item 1
* Item 2
Expand Down
38 changes: 38 additions & 0 deletions test/docs/incorrect-url.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# My Doc with TOC

***

* [Heading One](#heading-one)
* [Heading Two](#eading-two)
* [Sub Heading One](#sub-heading-one)
* [Sub Heading Three](#sub-heading-three)
* [Heading Three](#headingthree)
* [Heading Four](#heaing-four)

## Heading One

Lorem ipsum dolor sit amet.

## Heading Two

Lorem ipsum dolor sit amet.

### Sub Heading One

Lorem ipsum dolor sit amet.

### Sub Heading Three

Lorem ipsum dolor sit amet.

## Heading Three

Lorem ipsum dolor sit amet.

## Heading Four

* Item 1
* Item 2
* Sub Item 1
* [Sub Item Link](https://www.google.com/)
* `code block`
File renamed without changes.
Loading

0 comments on commit 374f11f

Please sign in to comment.