Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

doc: tests local links in markdown documents #32359

Closed
wants to merge 2 commits into from
Closed
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
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ test-doc: doc-only lint ## Builds, lints, and verifies the docs.
else \
$(PYTHON) tools/test.py $(PARALLEL_ARGS) doctool; \
fi
$(NODE) tools/doc/checkLinks.js .

test-known-issues: all
$(PYTHON) tools/test.py $(PARALLEL_ARGS) known_issues
Expand Down
8 changes: 4 additions & 4 deletions doc/guides/collaborator-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@

This document explains how Collaborators manage the Node.js project.
Collaborators should understand the
[guidelines for new contributors](CONTRIBUTING.md) and the
[project governance model](GOVERNANCE.md).
[guidelines for new contributors](../../CONTRIBUTING.md) and the
[project governance model](../../GOVERNANCE.md).

## Issues and Pull Requests

Expand All @@ -50,7 +50,7 @@ request. See [Who to CC in the issue tracker](#who-to-cc-in-the-issue-tracker).

Always show courtesy to individuals submitting issues and pull requests. Be
welcoming to first-time contributors, identified by the GitHub
![First-time contributor](./doc/first_timer_badge.png) badge.
![First-time contributor](../first_timer_badge.png) badge.

For first-time contributors, check if the commit author is the same as the pull
request author. This way, once their pull request lands, GitHub will show them
Expand Down Expand Up @@ -481,7 +481,7 @@ $ git checkout master
```

Update the tree (assumes your repo is set up as detailed in
[CONTRIBUTING.md](./doc/guides/contributing/pull-requests.md#step-1-fork)):
[CONTRIBUTING.md](./contributing/pull-requests.md#step-1-fork)):

```text
$ git fetch upstream
Expand Down
4 changes: 2 additions & 2 deletions doc/guides/cpp-style-guide.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# C++ Style Guide

See also the [C++ codebase README](src/README.md) for C++ idioms in the Node.js
codebase not related to stylistic issues.
See also the [C++ codebase README](../../src/README.md) for C++ idioms in the
Node.js codebase not related to stylistic issues.

## Table of Contents

Expand Down
4 changes: 2 additions & 2 deletions doc/guides/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ accordingly by removing the bold styling from the previous release.
If this release includes new APIs then it is necessary to document that they
were first added in this version. The relevant commits should already include
`REPLACEME` tags as per the example in the
[docs README](../tools/doc/README.md). Check for these tags with `grep REPLACEME
doc/api/*.md`, and substitute this node version with `sed -i
[docs README](../../tools/doc/README.md). Check for these tags with `grep
REPLACEME doc/api/*.md`, and substitute this node version with `sed -i
"s/REPLACEME/$VERSION/g" doc/api/*.md` or `perl -pi -e "s/REPLACEME/$VERSION/g"
doc/api/*.md`.

Expand Down
2 changes: 1 addition & 1 deletion onboarding.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ onboarding session.
* Be nice about closing issues! Let people know why, and that issues and PRs
can be reopened if necessary

* [**See "Labels"**](./onboarding-extras.md#labels)
* [**See "Labels"**](./doc/guides/onboarding-extras.md#labels)
* There is [a bot](https://github.com/nodejs-github-bot/github-bot) that
applies subsystem labels (for example, `doc`, `test`, `assert`, or `buffer`)
so that we know what parts of the code base the pull request modifies. It is
Expand Down
74 changes: 74 additions & 0 deletions tools/doc/checkLinks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

const fs = require('fs');
const { Worker, isMainThread, workerData: path } = require('worker_threads');

function* getLinksRecursively(node) {
if (
(node.type === 'link' && !node.url.startsWith('#')) ||
node.type === 'image'
) {
yield node;
}
for (const child of node.children || []) {
yield* getLinksRecursively(child);
}
}

if (isMainThread) {
const { extname, join, resolve } = require('path');
const DIR = resolve(process.argv[2]);

console.log('Running Markdown link checker...');

async function* findMarkdownFilesRecursively(dirPath) {
const fileNames = await fs.promises.readdir(dirPath);

for (const fileName of fileNames) {
const path = join(dirPath, fileName);

const stats = await fs.promises.stat(path);
if (
stats.isDirectory() &&
fileName !== 'api' &&
fileName !== 'deps' &&
fileName !== 'node_modules'
) {
yield* findMarkdownFilesRecursively(path);
} else if (extname(fileName) === '.md') {
yield path;
}
}
}

function errorHandler(error) {
console.error(error);
process.exitCode = 1;
}

setImmediate(async () => {
for await (const path of findMarkdownFilesRecursively(DIR)) {
new Worker(__filename, { workerData: path }).on('error', errorHandler);
}
});
} else {
const unified = require('unified');
const { pathToFileURL } = require('url');

const tree = unified()
.use(require('remark-parse'))
.parse(fs.readFileSync(path));

const base = pathToFileURL(path);
for (const node of getLinksRecursively(tree)) {
const targetURL = new URL(node.url, base);
if (targetURL.protocol === 'file:' && !fs.existsSync(targetURL)) {
const error = new Error('Broken link in a Markdown document.');
const { start } = node.position;
error.stack =
error.stack.substring(0, error.stack.indexOf('\n') + 5) +
`at ${node.type} (${path}:${start.line}:${start.column})`;
throw error;
}
}
}