or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

index.md

index.mddocs/

0

# remark-validate-links

1

2

remark-validate-links is a unified remark plugin that validates local markdown links and images in Git repositories. It checks that links point to existing files and headings, working offline for fast and reliable link validation. The plugin integrates with the remark ecosystem and provides Git-aware validation for hosted services like GitHub, GitLab, and Bitbucket.

3

4

## Package Information

5

6

- **Package Name**: remark-validate-links

7

- **Package Type**: npm

8

- **Language**: JavaScript (with TypeScript definitions)

9

- **Installation**: `npm install remark-validate-links`

10

11

## Core Imports

12

13

```javascript

14

import remarkValidateLinks from "remark-validate-links";

15

```

16

17

For CommonJS:

18

19

```javascript

20

const remarkValidateLinks = require("remark-validate-links");

21

```

22

23

**Note:** This package exports only a default export function - no named exports are available. TypeScript types `Options` and `UrlConfig` are defined via JSDoc comments in the source code for TypeScript support, but cannot be imported at runtime.

24

25

## Basic Usage

26

27

```javascript

28

import remarkValidateLinks from "remark-validate-links";

29

import { remark } from "remark";

30

import { read } from "to-vfile";

31

32

// Basic usage with default options

33

const file = await remark()

34

.use(remarkValidateLinks)

35

.process(await read("example.md"));

36

37

// Usage with configuration options

38

const file = await remark()

39

.use(remarkValidateLinks, {

40

repository: "https://github.com/user/repo",

41

root: "./docs",

42

skipPathPatterns: [/\/temp\//, "ignore-me.md"]

43

})

44

.process(await read("example.md"));

45

```

46

47

## Architecture

48

49

remark-validate-links operates as a remark plugin that:

50

51

- **Link Collection**: Visits all link and image nodes in the markdown AST to collect references

52

- **Repository Detection**: Auto-detects Git repository information and remote URLs using git commands

53

- **Path Resolution**: Converts various link formats (relative, absolute, URLs) to local file paths

54

- **File Validation**: Checks existence of referenced files and directories (Node.js only)

55

- **Heading Validation**: Validates heading anchors within the same file (API) or across files (CLI)

56

- **Error Reporting**: Generates detailed warnings with suggestions for missing references

57

- **Platform Adaptation**: Provides full functionality in Node.js and limited functionality in browsers

58

59

## Capabilities

60

61

### Main Plugin Function

62

63

Creates a remark transformer that validates local links and images in markdown documents.

64

65

```javascript { .api }

66

/**

67

* Check that markdown links and images point to existing local files and headings in a Git repo.

68

*

69

* ⚠️ **Important**: The API in Node.js checks links to headings and files

70

* but does not check whether headings in other files exist.

71

* The API in browsers only checks links to headings in the same file.

72

* The CLI can check everything.

73

*

74

* @param {Options | null | undefined} [options] - Configuration (optional)

75

* @param {FileSet | null | undefined} [fileSet] - File set (optional)

76

* @returns {Function} Transform function

77

*/

78

function remarkValidateLinks(options, fileSet);

79

```

80

81

**Usage Examples:**

82

83

```javascript

84

import remarkValidateLinks from "remark-validate-links";

85

import { remark } from "remark";

86

87

// Basic usage

88

const processor = remark().use(remarkValidateLinks);

89

90

// With options

91

const processor = remark().use(remarkValidateLinks, {

92

repository: false, // disable repository detection

93

skipPathPatterns: [/node_modules/]

94

});

95

96

// For CLI usage with file set - enables cross-file validation

97

const fileSet = new FileSet();

98

const processor = remark().use(remarkValidateLinks, {}, fileSet);

99

// When fileSet is provided:

100

// - Plugin discovers referenced files and adds them for processing

101

// - Cross-file heading validation becomes available

102

// - Directory references resolve to readme files (.md, .markdown, .mdown, .mkdn)

103

```

104

105

### Link Validation Features

106

107

The plugin validates these types of links:

108

109

- **Same-file headings**: `#heading-id`

110

- **Relative file links**: `./path/to/file.md`, `../other.js`

111

- **Files with headings**: `./file.md#heading`

112

- **Absolute paths**: `/path/from/repo/root.md` (when repository is configured)

113

- **Hosted Git URLs**: Full URLs to GitHub/GitLab/Bitbucket files in the same repository

114

- **Line number links**: GitHub and GitLab style line links like `file.js#L123` (when `lines: true`)

115

- **Branch-aware URLs**: Handles branch prefixes in repository URLs (currently assumes main/master branch)

116

117

**Error Detection:**

118

119

- **Missing files**: Reports when linked files don't exist with file paths

120

- **Missing headings**: Reports when heading anchors don't exist within files

121

- **Typo suggestions**: Provides "did you mean" suggestions using edit distance algorithms

122

- **Context-aware errors**: Different error messages for headings vs files vs headings in other files

123

124

## Types

125

126

### Options Interface

127

128

Configuration object for the plugin.

129

130

```javascript { .api }

131

/**

132

* Configuration options for the plugin.

133

*/

134

interface Options {

135

/**

136

* URL to hosted Git (default: detected from Git remote);

137

* if you're not in a Git repository, you must pass `false`;

138

* if the repository resolves to something npm understands as a Git host such

139

* as GitHub, GitLab, or Bitbucket, full URLs to that host (say

140

* `https://github.com/remarkjs/remark-validate-links/readme.md#install`) are

141

* checked.

142

*/

143

repository?: string | false | null | undefined;

144

/**

145

* Path to Git root folder (default: local Git folder);

146

* if both `root` and `repository` are nullish, the Git root is detected;

147

* if `root` is not given but `repository` is, `file.cwd` is used.

148

*/

149

root?: string | null | undefined;

150

/**

151

* List of patterns for *paths* that should be skipped;

152

* each absolute local path + hash will be tested against each pattern and

153

* will be ignored if `new RegExp(pattern).test(value) === true`;

154

* example values are then `/Users/tilde/path/to/repo/readme.md#some-heading`.

155

*/

156

skipPathPatterns?: ReadonlyArray<RegExp | string> | null | undefined;

157

/**

158

* Config on how hosted Git works (default: detected from repo);

159

* `github.com`, `gitlab.com`, or `bitbucket.org` work automatically;

160

* otherwise, pass `urlConfig` manually.

161

*/

162

urlConfig?: UrlConfig | null | undefined;

163

}

164

```

165

166

### UrlConfig Interface

167

168

Configuration for hosted Git services like GitHub, GitLab, and Bitbucket.

169

170

```javascript { .api }

171

/**

172

* Hosted Git info configuration.

173

*

174

* For this repository (`remarkjs/remark-validate-links` on GitHub)

175

* `urlConfig` looks as follows:

176

*

177

* ```js

178

* {

179

* // Domain of URLs:

180

* hostname: 'github.com',

181

* // Path prefix before files:

182

* prefix: '/remarkjs/remark-validate-links/blob/',

183

* // Prefix of headings:

184

* headingPrefix: '#',

185

* // Hash to top of markdown documents:

186

* topAnchor: '#readme',

187

* // Whether lines in files can be linked:

188

* lines: true

189

* }

190

* ```

191

*

192

* If this project were hosted on Bitbucket, it would be:

193

*

194

* ```js

195

* {

196

* hostname: 'bitbucket.org',

197

* prefix: '/remarkjs/remark-validate-links/src/',

198

* headingPrefix: '#markdown-header-',

199

* lines: false

200

* }

201

* ```

202

*/

203

interface UrlConfig {

204

/** Prefix of headings (example: `'#'`, `'#markdown-header-'`) */

205

headingPrefix?: string | null | undefined;

206

/** Domain of URLs (example: `'github.com'`, `'bitbucket.org'`) */

207

hostname?: string | null | undefined;

208

/** Whether absolute paths (`/x/y/z.md`) resolve relative to a repo */

209

resolveAbsolutePathsInRepo?: boolean | null | undefined;

210

/** Whether lines in files can be linked */

211

lines?: boolean | null | undefined;

212

/** Path prefix before files (example: `'/remarkjs/remark-validate-links/blob/'`, `'/remarkjs/remark-validate-links/src/'`) */

213

prefix?: string | null | undefined;

214

/** Hash to top of readme (example: `#readme`) */

215

topAnchor?: string | null | undefined;

216

}

217

```

218

219

**Pre-configured services:**

220

221

- **GitHub**: `headingPrefix: '#'`, `lines: true`, `topAnchor: '#readme'`, `prefix: '/owner/repo/blob/'`, `resolveAbsolutePathsInRepo: true`

222

- **GitLab**: `headingPrefix: '#'`, `lines: true`, `topAnchor: '#readme'`, `prefix: '/owner/repo/blob/'`

223

- **Bitbucket**: `headingPrefix: '#markdown-header-'`, `lines: false`, `prefix: '/owner/repo/src/'`

224

225

**Automatic detection**: The plugin uses the `hosted-git-info` library to automatically detect the Git service type from the repository URL and apply appropriate configuration.

226

227

### Internal Types

228

229

Types used internally by the plugin for reference tracking and validation.

230

231

```javascript { .api }

232

/** Map of file paths to their available headings/anchors */

233

type Landmarks = Map<string, Map<string, boolean>>;

234

235

/** Reference to a file and optional heading */

236

interface Reference {

237

/** Absolute path to the referenced file */

238

filePath: string;

239

/** Hash/anchor portion of the reference */

240

hash?: string | undefined;

241

}

242

243

/** VFile from vfile package for file representation */

244

type VFile = import('vfile').VFile;

245

246

/** FileSet from unified-engine package for CLI batch processing */

247

type FileSet = import('unified-engine').FileSet;

248

249

/** Nodes type from mdast package */

250

type Nodes = import('mdast').Nodes;

251

252

/** Resource type from mdast package (link and image nodes) */

253

type Resource = import('mdast').Resource;

254

255

/** Link and image nodes from mdast AST */

256

type Resources = Extract<Nodes, Resource>;

257

258

/** Information about a reference including source context */

259

interface ReferenceInfo {

260

/** Source file containing the reference */

261

file: VFile;

262

/** The reference details */

263

reference: Reference;

264

/** AST nodes that contain this reference */

265

nodes: ReadonlyArray<Resources>;

266

}

267

268

/** Internal state passed between validation functions */

269

interface State {

270

/** Directory of the current file */

271

base: string;

272

/** Absolute path to the current file */

273

path: string;

274

/** Path to Git root directory */

275

root?: string | null | undefined;

276

/** Compiled skip patterns */

277

skipPathPatterns: ReadonlyArray<RegExp>;

278

/** URL configuration for the repository */

279

urlConfig: UrlConfig;

280

}

281

282

/** Plugin constants for error reporting and data storage */

283

interface Constants {

284

/** Rule ID for missing file errors */

285

fileRuleId: 'missing-file';

286

/** Rule ID for missing heading in other file errors */

287

headingInFileRuleId: 'missing-heading-in-file';

288

/** Rule ID for missing heading in current file errors */

289

headingRuleId: 'missing-heading';

290

/** Data key for storing landmarks in VFile data */

291

landmarkId: 'remarkValidateLinksLandmarks';

292

/** Data key for storing references in VFile data */

293

referenceId: 'remarkValidateLinksReferences';

294

/** Source identifier for error messages */

295

sourceId: 'remark-validate-links';

296

}

297

```

298

299

## Platform Differences

300

301

### Node.js Environment

302

303

Full functionality with platform-specific implementations:

304

305

- **File existence checking**: Uses Node.js `fs.access()` to verify file existence

306

- **Cross-file heading validation**: Available in CLI mode when using `FileSet`

307

- **Git repository detection**: Executes `git remote -v` and `git rev-parse --show-cdup` commands

308

- **Hosted Git URL validation**: Full support for GitHub, GitLab, and Bitbucket URL patterns

309

- **Directory handling**: Automatically resolves readme files in directories

310

- **Pattern matching**: Full RegExp support for `skipPathPatterns`

311

312

### Browser Environment

313

314

Limited functionality with browser-safe implementations:

315

316

- **Same-file heading validation only**: Cannot access filesystem or execute git commands

317

- **No file existence checking**: `checkFiles()` function is a no-op stub

318

- **No Git repository detection**: `findRepo()` function is a no-op stub

319

- **Manual configuration required**: Must provide `repository` and `root` options explicitly

320

- **Local anchor validation**: Can still validate `#heading` links within the same document

321

322

## Error Messages

323

324

The plugin generates detailed error messages with specific rule IDs:

325

326

- **`missing-file`**: Referenced file does not exist

327

- **`missing-heading`**: Heading anchor not found in current file

328

- **`missing-heading-in-file`**: Heading anchor not found in referenced file

329

330

Error messages include:

331

332

- **Contextual descriptions**: "Cannot find heading for `#heading-name`" or "Cannot find file `path/to/file.md`"

333

- **File context**: For cross-file references, shows both the missing anchor and target file

334

- **Smart suggestions**: Uses the `propose` library with edit distance algorithms (70% similarity threshold) to suggest corrections

335

- **Source attribution**: All errors include source URL `https://github.com/remarkjs/remark-validate-links#readme`

336

- **Position information**: Errors point to the exact location of problematic links in the source markdown

337

338

**Example error output:**

339

```

340

example.md:5:10-5:32: Cannot find heading for `#non-existent` in `readme.md`; did you mean `#installation`? [missing-heading-in-file](https://github.com/remarkjs/remark-validate-links#readme)

341

```

342

343

## Integration

344

345

### CLI Usage

346

347

```bash

348

# Check single file

349

npx remark example.md --use remark-validate-links --quiet

350

351

# Check multiple files

352

npx remark . --ext md --use remark-validate-links --quiet

353

```

354

355

### Programmatic Usage

356

357

```javascript

358

import remarkValidateLinks from "remark-validate-links";

359

import { remark } from "remark";

360

import { unified } from "unified";

361

import { reporter } from "vfile-reporter";

362

import { read } from "to-vfile";

363

364

// Process single file

365

const file = await remark()

366

.use(remarkValidateLinks)

367

.process(await read("document.md"));

368

369

console.log(reporter(file));

370

371

// Process multiple files with unified-engine

372

import { engine } from "unified-engine";

373

374

engine({

375

processor: remark().use(remarkValidateLinks),

376

files: ["*.md"],

377

extensions: ["md"],

378

pluginPrefix: "remark",

379

quiet: true

380

}, (error, code) => {

381

if (error) throw error;

382

process.exit(code);

383

});

384

```