or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.md
tile.json

tessl/npm-remark-validate-links

remark plugin to validate links to headings and files in Git repositories

Workspace
tessl
Visibility
Public
Created
Last updated
Describes
npmpkg:npm/remark-validate-links@13.1.x

To install, run

npx @tessl/cli install tessl/npm-remark-validate-links@13.1.0

index.mddocs/

remark-validate-links

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.

Package Information

  • Package Name: remark-validate-links
  • Package Type: npm
  • Language: JavaScript (with TypeScript definitions)
  • Installation: npm install remark-validate-links

Core Imports

import remarkValidateLinks from "remark-validate-links";

For CommonJS:

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

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.

Basic Usage

import remarkValidateLinks from "remark-validate-links";
import { remark } from "remark";
import { read } from "to-vfile";

// Basic usage with default options
const file = await remark()
  .use(remarkValidateLinks)
  .process(await read("example.md"));

// Usage with configuration options
const file = await remark()
  .use(remarkValidateLinks, {
    repository: "https://github.com/user/repo",
    root: "./docs",
    skipPathPatterns: [/\/temp\//, "ignore-me.md"]
  })
  .process(await read("example.md"));

Architecture

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

  • Link Collection: Visits all link and image nodes in the markdown AST to collect references
  • Repository Detection: Auto-detects Git repository information and remote URLs using git commands
  • Path Resolution: Converts various link formats (relative, absolute, URLs) to local file paths
  • File Validation: Checks existence of referenced files and directories (Node.js only)
  • Heading Validation: Validates heading anchors within the same file (API) or across files (CLI)
  • Error Reporting: Generates detailed warnings with suggestions for missing references
  • Platform Adaptation: Provides full functionality in Node.js and limited functionality in browsers

Capabilities

Main Plugin Function

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

/**
 * Check that markdown links and images point to existing local files and headings in a Git repo.
 * 
 * ⚠️ **Important**: The API in Node.js checks links to headings and files
 * but does not check whether headings in other files exist.
 * The API in browsers only checks links to headings in the same file.
 * The CLI can check everything.
 * 
 * @param {Options | null | undefined} [options] - Configuration (optional)
 * @param {FileSet | null | undefined} [fileSet] - File set (optional) 
 * @returns {Function} Transform function
 */
function remarkValidateLinks(options, fileSet);

Usage Examples:

import remarkValidateLinks from "remark-validate-links";
import { remark } from "remark";

// Basic usage
const processor = remark().use(remarkValidateLinks);

// With options
const processor = remark().use(remarkValidateLinks, {
  repository: false, // disable repository detection
  skipPathPatterns: [/node_modules/]
});

// For CLI usage with file set - enables cross-file validation
const fileSet = new FileSet();
const processor = remark().use(remarkValidateLinks, {}, fileSet);
// When fileSet is provided:
// - Plugin discovers referenced files and adds them for processing
// - Cross-file heading validation becomes available
// - Directory references resolve to readme files (.md, .markdown, .mdown, .mkdn)

Link Validation Features

The plugin validates these types of links:

  • Same-file headings: #heading-id
  • Relative file links: ./path/to/file.md, ../other.js
  • Files with headings: ./file.md#heading
  • Absolute paths: /path/from/repo/root.md (when repository is configured)
  • Hosted Git URLs: Full URLs to GitHub/GitLab/Bitbucket files in the same repository
  • Line number links: GitHub and GitLab style line links like file.js#L123 (when lines: true)
  • Branch-aware URLs: Handles branch prefixes in repository URLs (currently assumes main/master branch)

Error Detection:

  • Missing files: Reports when linked files don't exist with file paths
  • Missing headings: Reports when heading anchors don't exist within files
  • Typo suggestions: Provides "did you mean" suggestions using edit distance algorithms
  • Context-aware errors: Different error messages for headings vs files vs headings in other files

Types

Options Interface

Configuration object for the plugin.

/**
 * Configuration options for the plugin.
 */
interface Options {
  /**
   * URL to hosted Git (default: detected from Git remote);
   * if you're not in a Git repository, you must pass `false`;
   * if the repository resolves to something npm understands as a Git host such
   * as GitHub, GitLab, or Bitbucket, full URLs to that host (say
   * `https://github.com/remarkjs/remark-validate-links/readme.md#install`) are
   * checked.
   */
  repository?: string | false | null | undefined;
  /**
   * Path to Git root folder (default: local Git folder);
   * if both `root` and `repository` are nullish, the Git root is detected;
   * if `root` is not given but `repository` is, `file.cwd` is used.
   */
  root?: string | null | undefined;
  /**
   * List of patterns for *paths* that should be skipped;
   * each absolute local path + hash will be tested against each pattern and
   * will be ignored if `new RegExp(pattern).test(value) === true`;
   * example values are then `/Users/tilde/path/to/repo/readme.md#some-heading`.
   */
  skipPathPatterns?: ReadonlyArray<RegExp | string> | null | undefined;
  /**
   * Config on how hosted Git works (default: detected from repo);
   * `github.com`, `gitlab.com`, or `bitbucket.org` work automatically;
   * otherwise, pass `urlConfig` manually.
   */
  urlConfig?: UrlConfig | null | undefined;
}

UrlConfig Interface

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

/**
 * Hosted Git info configuration.
 * 
 * For this repository (`remarkjs/remark-validate-links` on GitHub)
 * `urlConfig` looks as follows:
 * 
 * ```js
 * {
 *   // Domain of URLs:
 *   hostname: 'github.com',
 *   // Path prefix before files:
 *   prefix: '/remarkjs/remark-validate-links/blob/',
 *   // Prefix of headings:
 *   headingPrefix: '#',
 *   // Hash to top of markdown documents:
 *   topAnchor: '#readme',
 *   // Whether lines in files can be linked:
 *   lines: true
 * }
 * ```
 * 
 * If this project were hosted on Bitbucket, it would be:
 * 
 * ```js
 * {
 *   hostname: 'bitbucket.org',
 *   prefix: '/remarkjs/remark-validate-links/src/',
 *   headingPrefix: '#markdown-header-',
 *   lines: false
 * }
 * ```
 */
interface UrlConfig {
  /** Prefix of headings (example: `'#'`, `'#markdown-header-'`) */
  headingPrefix?: string | null | undefined;
  /** Domain of URLs (example: `'github.com'`, `'bitbucket.org'`) */
  hostname?: string | null | undefined;
  /** Whether absolute paths (`/x/y/z.md`) resolve relative to a repo */
  resolveAbsolutePathsInRepo?: boolean | null | undefined;
  /** Whether lines in files can be linked */
  lines?: boolean | null | undefined;
  /** Path prefix before files (example: `'/remarkjs/remark-validate-links/blob/'`, `'/remarkjs/remark-validate-links/src/'`) */
  prefix?: string | null | undefined;
  /** Hash to top of readme (example: `#readme`) */
  topAnchor?: string | null | undefined;
}

Pre-configured services:

  • GitHub: headingPrefix: '#', lines: true, topAnchor: '#readme', prefix: '/owner/repo/blob/', resolveAbsolutePathsInRepo: true
  • GitLab: headingPrefix: '#', lines: true, topAnchor: '#readme', prefix: '/owner/repo/blob/'
  • Bitbucket: headingPrefix: '#markdown-header-', lines: false, prefix: '/owner/repo/src/'

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.

Internal Types

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

/** Map of file paths to their available headings/anchors */
type Landmarks = Map<string, Map<string, boolean>>;

/** Reference to a file and optional heading */
interface Reference {
  /** Absolute path to the referenced file */
  filePath: string;
  /** Hash/anchor portion of the reference */
  hash?: string | undefined;
}

/** VFile from vfile package for file representation */
type VFile = import('vfile').VFile;

/** FileSet from unified-engine package for CLI batch processing */
type FileSet = import('unified-engine').FileSet;

/** Nodes type from mdast package */  
type Nodes = import('mdast').Nodes;

/** Resource type from mdast package (link and image nodes) */
type Resource = import('mdast').Resource;

/** Link and image nodes from mdast AST */
type Resources = Extract<Nodes, Resource>;

/** Information about a reference including source context */
interface ReferenceInfo {
  /** Source file containing the reference */
  file: VFile;
  /** The reference details */
  reference: Reference;
  /** AST nodes that contain this reference */
  nodes: ReadonlyArray<Resources>;
}

/** Internal state passed between validation functions */
interface State {
  /** Directory of the current file */
  base: string;
  /** Absolute path to the current file */
  path: string;
  /** Path to Git root directory */
  root?: string | null | undefined;
  /** Compiled skip patterns */
  skipPathPatterns: ReadonlyArray<RegExp>;
  /** URL configuration for the repository */
  urlConfig: UrlConfig;
}

/** Plugin constants for error reporting and data storage */
interface Constants {
  /** Rule ID for missing file errors */
  fileRuleId: 'missing-file';
  /** Rule ID for missing heading in other file errors */
  headingInFileRuleId: 'missing-heading-in-file';
  /** Rule ID for missing heading in current file errors */
  headingRuleId: 'missing-heading';
  /** Data key for storing landmarks in VFile data */
  landmarkId: 'remarkValidateLinksLandmarks';
  /** Data key for storing references in VFile data */
  referenceId: 'remarkValidateLinksReferences';
  /** Source identifier for error messages */
  sourceId: 'remark-validate-links';
}

Platform Differences

Node.js Environment

Full functionality with platform-specific implementations:

  • File existence checking: Uses Node.js fs.access() to verify file existence
  • Cross-file heading validation: Available in CLI mode when using FileSet
  • Git repository detection: Executes git remote -v and git rev-parse --show-cdup commands
  • Hosted Git URL validation: Full support for GitHub, GitLab, and Bitbucket URL patterns
  • Directory handling: Automatically resolves readme files in directories
  • Pattern matching: Full RegExp support for skipPathPatterns

Browser Environment

Limited functionality with browser-safe implementations:

  • Same-file heading validation only: Cannot access filesystem or execute git commands
  • No file existence checking: checkFiles() function is a no-op stub
  • No Git repository detection: findRepo() function is a no-op stub
  • Manual configuration required: Must provide repository and root options explicitly
  • Local anchor validation: Can still validate #heading links within the same document

Error Messages

The plugin generates detailed error messages with specific rule IDs:

  • missing-file: Referenced file does not exist
  • missing-heading: Heading anchor not found in current file
  • missing-heading-in-file: Heading anchor not found in referenced file

Error messages include:

  • Contextual descriptions: "Cannot find heading for #heading-name" or "Cannot find file path/to/file.md"
  • File context: For cross-file references, shows both the missing anchor and target file
  • Smart suggestions: Uses the propose library with edit distance algorithms (70% similarity threshold) to suggest corrections
  • Source attribution: All errors include source URL https://github.com/remarkjs/remark-validate-links#readme
  • Position information: Errors point to the exact location of problematic links in the source markdown

Example error output:

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)

Integration

CLI Usage

# Check single file
npx remark example.md --use remark-validate-links --quiet

# Check multiple files
npx remark . --ext md --use remark-validate-links --quiet

Programmatic Usage

import remarkValidateLinks from "remark-validate-links";
import { remark } from "remark";
import { unified } from "unified";
import { reporter } from "vfile-reporter";
import { read } from "to-vfile";

// Process single file
const file = await remark()
  .use(remarkValidateLinks)
  .process(await read("document.md"));

console.log(reporter(file));

// Process multiple files with unified-engine
import { engine } from "unified-engine";

engine({
  processor: remark().use(remarkValidateLinks),
  files: ["*.md"],
  extensions: ["md"],
  pluginPrefix: "remark",
  quiet: true
}, (error, code) => {
  if (error) throw error;
  process.exit(code);
});