Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

remark-validate-links

Package Overview
Dependencies
Maintainers
2
Versions
35
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

remark-validate-links - npm Package Compare versions

Comparing version 10.0.4 to 12.1.1

index.d.ts

7

index.js

@@ -1,3 +0,6 @@

'use strict'
/**
* @typedef {import('./lib/index.js').UrlConfig} UrlConfig
* @typedef {import('./lib/index.js').Options} Options
*/
module.exports = require('./lib')
export {default} from './lib/index.js'

@@ -1,7 +0,1 @@

'use strict'
module.exports = checkIfReferencedFilesExist
function checkIfReferencedFilesExist(ctx, next) {
next()
}
export async function checkFiles() {}

@@ -1,44 +0,41 @@

'use strict'
/**
* @typedef {import('../types.js').VFile} VFile
* @typedef {import('../types.js').Landmarks} Landmarks
* @typedef {import('../types.js').References} References
*/
var fs = require('fs')
import {constants, promises as fs} from 'node:fs'
module.exports = checkIfReferencedFilesExist
/**
* @param {{files: VFile[], landmarks: Landmarks, references: References}} ctx
*/
export async function checkFiles(ctx) {
const landmarks = ctx.landmarks
const references = ctx.references
/** @type {Array.<Promise<void>>} */
const promises = []
/** @type {string} */
let filePath
function checkIfReferencedFilesExist(ctx, next) {
var landmarks = ctx.landmarks
var references = ctx.references
var filePaths = []
var filePath
var actual = 0
var expected
for (filePath in references) {
if (landmarks[filePath] === undefined) {
filePaths.push(filePath)
}
}
/** @type {Record<string, boolean>} */
const map = Object.create(null)
expected = filePaths.length
landmarks[filePath] = map
if (expected === 0) {
next()
} else {
filePaths.forEach(checkIfExists)
promises.push(
fs.access(filePath, constants.F_OK).then(
() => {
map[''] = true
},
(/** @type {NodeJS.ErrnoException} */ error) => {
map[''] = error.code !== 'ENOENT' && error.code !== 'ENOTDIR'
}
)
)
}
}
function checkIfExists(filePath) {
fs.access(filePath, fs.F_OK, onaccess)
function onaccess(error) {
var noEntry =
error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')
landmarks[filePath] = Object.create(null)
landmarks[filePath][''] = !noEntry
if (++actual === expected) {
next()
}
}
}
await Promise.all(promises)
}

@@ -1,13 +0,11 @@

'use strict'
import {trough} from 'trough'
import {mergeLandmarks} from './merge-landmarks.js'
import {mergeReferences} from './merge-references.js'
import {checkFiles} from './check-files.js'
import {validate} from './validate.js'
var trough = require('trough')
var mergeLandmarks = require('./merge-landmarks')
var mergeReferences = require('./merge-references')
var checkIfReferencedFilesExist = require('./check-files')
var validate = require('./validate')
module.exports = trough()
export const check = trough()
.use(mergeLandmarks)
.use(mergeReferences)
.use(checkIfReferencedFilesExist)
.use(checkFiles)
.use(validate)

@@ -1,26 +0,35 @@

'use strict'
/**
* @typedef {import('../types.js').VFile} VFile
* @typedef {import('../types.js').Landmarks} Landmarks
*/
var constants = require('../constants')
import {constants} from '../constants.js'
module.exports = mergeLandmarks
const own = {}.hasOwnProperty
function mergeLandmarks(ctx) {
var result = Object.create(null)
var files = ctx.files
var length = files.length
var index = -1
var file
var landmarks
var landmark
/**
* @param {{files: VFile[], landmarks?: Landmarks}} ctx
*/
export function mergeLandmarks(ctx) {
/** @type {Landmarks} */
const result = Object.create(null)
const files = ctx.files
let index = -1
while (++index < length) {
file = files[index]
landmarks = file.data[constants.landmarkId]
while (++index < files.length) {
const file = files[index]
const landmarks = /** @type {Landmarks|undefined} */ (
file.data[constants.landmarkId]
)
/** @type {string} */
let landmark
if (landmarks) {
for (landmark in landmarks) {
result[landmark] = Object.assign(
Object.create(null),
landmarks[landmark]
)
if (own.call(landmarks, landmark)) {
result[landmark] = Object.assign(
Object.create(null),
landmarks[landmark]
)
}
}

@@ -27,0 +36,0 @@ }

@@ -1,22 +0,30 @@

'use strict'
/**
* @typedef {import('../types.js').VFile} VFile
* @typedef {import('../types.js').Landmarks} Landmarks
* @typedef {import('../types.js').References} References
* @typedef {import('../types.js').ReferenceMap} ReferenceMap
* @typedef {import('../types.js').Resource} Resource
*/
var constants = require('../constants')
import {constants} from '../constants.js'
module.exports = mergeReferences
const own = {}.hasOwnProperty
function mergeReferences(ctx) {
var result = {}
var files = ctx.files
var length = files.length
var index = -1
var file
var references
var reference
var all
var internal
var hash
/**
* @param {{files: VFile[], landmarks: Landmarks, references?: References}} ctx
*/
export function mergeReferences(ctx) {
/** @type {References} */
const result = {}
const files = ctx.files
let index = -1
while (++index < length) {
file = files[index]
references = file.data[constants.referenceId]
while (++index < files.length) {
const file = files[index]
const references =
/** @type {Record<string, Record<string, Resource[]>>|undefined} */ (
file.data[constants.referenceId]
)
/** @type {string} */
let reference

@@ -28,14 +36,23 @@ if (!references) {

for (reference in references) {
internal = references[reference]
all =
reference in result
? result[reference]
: (result[reference] = Object.create(null))
if (own.call(references, reference)) {
const internal = references[reference]
/** @type {Record<string, ReferenceMap[]>} */
const all =
reference in result
? result[reference]
: (result[reference] = Object.create(null))
/** @type {string} */
let hash
for (hash in internal) {
;(all[hash] || (all[hash] = [])).push({
file: file,
reference: {filePath: reference, hash: hash},
nodes: internal[hash]
})
for (hash in internal) {
// eslint-disable-next-line max-depth
if (own.call(internal, hash)) {
const list = all[hash] || (all[hash] = [])
list.push({
file,
reference: {filePath: reference, hash},
nodes: internal[hash]
})
}
}
}

@@ -42,0 +59,0 @@ }

@@ -1,28 +0,41 @@

'use strict'
/**
* @typedef {import('../types.js').VFile} VFile
* @typedef {import('../types.js').Landmarks} Landmarks
* @typedef {import('../types.js').References} References
* @typedef {import('../types.js').ReferenceMap} ReferenceMap
*/
var path = require('path')
var propose = require('propose')
var constants = require('../constants')
import path from 'node:path'
// @ts-expect-error: untyped.
import propose from 'propose'
import {constants} from '../constants.js'
module.exports = validate
const own = {}.hasOwnProperty
function validate(ctx) {
var landmarks = ctx.landmarks
var references = ctx.references
var missing = []
var reference
var hash
var refs
var lands
var length
var index
/**
* @param {{files: VFile[], landmarks: Landmarks, references: References}} ctx
*/
export function validate(ctx) {
const landmarks = ctx.landmarks
const references = ctx.references
/** @type {ReferenceMap[]} */
const missing = []
/** @type {string} */
let key
for (reference in references) {
refs = references[reference]
/* istanbul ignore next - `else` could happen in browser. */
lands = reference in landmarks ? landmarks[reference] : Object.create(null)
for (key in references) {
if (own.call(references, key)) {
const refs = references[key]
/** @type {Landmarks} */
const lands =
// `else` could happen in browser.
/* c8 ignore next */
key in landmarks ? landmarks[key] : Object.create(null)
/** @type {string} */
let hash
for (hash in refs) {
if (!lands[hash]) {
missing = missing.concat(refs[hash])
for (hash in refs) {
if (!lands[hash]) {
missing.push(...refs[hash])
}
}

@@ -32,25 +45,29 @@ }

length = missing.length
index = -1
let index = -1
while (++index < length) {
reference = missing[index]
warn(ctx, reference.reference, reference.file, reference.nodes)
while (++index < missing.length) {
warn(ctx, missing[index])
}
}
function warn(ctx, info, file, nodes) {
var landmarks = ctx.landmarks
var absolute = file.path ? path.resolve(file.cwd, file.path) : ''
var base = absolute ? path.dirname(absolute) : null
var filePath = base ? path.relative(base, info.filePath) : info.filePath
var hash = info.hash
var dictionary = []
var subhash
var landmark
var relativeLandmark
var reason
var ruleId
var origin
var suggestion
/**
* @param {{files: VFile[], landmarks: Landmarks, references: References}} ctx
* @param {ReferenceMap} reference
*/
function warn(ctx, reference) {
const landmarks = ctx.landmarks
const absolute = reference.file.path
? path.resolve(reference.file.cwd, reference.file.path)
: ''
const base = absolute ? path.dirname(absolute) : null
const filePath = base
? path.relative(base, reference.reference.filePath)
: reference.reference.filePath
const hash = reference.reference.hash
/** @type {string[]} */
const dictionary = []
/** @type {string} */
let reason
/** @type {string} */
let ruleId

@@ -72,3 +89,5 @@ if (hash) {

origin = [constants.sourceId, ruleId].join(':')
const origin = [constants.sourceId, ruleId].join(':')
/** @type {string} */
let landmark

@@ -81,3 +100,3 @@ for (landmark in landmarks) {

relativeLandmark = base ? path.relative(base, landmark) : landmark
const relativeLandmark = base ? path.relative(base, landmark) : landmark

@@ -93,2 +112,5 @@ if (!hash) {

/** @type {string} */
let subhash
for (subhash in landmarks[landmark]) {

@@ -101,3 +123,6 @@ if (subhash !== '') {

suggestion = propose(hash ? hash : filePath, dictionary, {threshold: 0.7})
/** @type {string} */
const suggestion = propose(hash ? hash : filePath, dictionary, {
threshold: 0.7
})

@@ -108,7 +133,12 @@ if (suggestion) {

nodes.forEach(one)
let index = -1
function one(node) {
file.message(reason, node, origin)
while (++index < reference.nodes.length) {
const message = reference.file.message(
reason,
reference.nodes[index],
origin
)
message.url = 'https://github.com/remarkjs/remark-validate-links#readme'
}
}

@@ -1,8 +0,8 @@

'use strict'
exports.sourceId = 'remark-validate-links'
exports.headingRuleId = 'missing-heading'
exports.headingInFileRuleId = 'missing-heading-in-file'
exports.fileRuleId = 'missing-file'
exports.landmarkId = 'remarkValidateLinksLandmarks'
exports.referenceId = 'remarkValidateLinksReferences'
export const constants = {
sourceId: 'remark-validate-links',
headingRuleId: 'missing-heading',
headingInFileRuleId: 'missing-heading-in-file',
fileRuleId: 'missing-file',
landmarkId: 'remarkValidateLinksLandmarks',
referenceId: 'remarkValidateLinksReferences'
}

@@ -1,54 +0,69 @@

'use strict'
/**
* @typedef {import('mdast').Root} Root
* @typedef {import('vfile').VFile} VFile
* @typedef {import('unified-engine').FileSet} FileSet
* @typedef {import('hosted-git-info').Hosts} Hosts
* @typedef {import('../index.js').Options} Options
* @typedef {import('../index.js').UrlConfig} UrlConfig
*/
var hostedGitInfo = require('hosted-git-info')
import hostedGitInfo from 'hosted-git-info'
module.exports = config
/** @type {Partial<Record<Hosts, string>>} */
const viewPaths = {github: 'blob', gitlab: 'blob', bitbucket: 'src'}
/** @type {Partial<Record<Hosts, string>>} */
const headingPrefixes = {
github: '#',
gitlab: '#',
bitbucket: '#markdown-header-'
}
/** @type {Partial<Record<Hosts, string>>} */
const topAnchors = {github: '#readme', gitlab: '#readme'}
/** @type {Partial<Record<Hosts, boolean>>} */
const lineLinks = {github: true, gitlab: true}
var viewPaths = {github: 'blob', gitlab: 'blob', bitbucket: 'src'}
var headingPrefixes = {github: '#', gitlab: '#', bitbucket: '#markdown-header-'}
var topAnchors = {github: '#readme', gitlab: '#readme'}
var lineLinks = {github: true, gitlab: true}
/**
* @param {{tree: Root, file: VFile, fileSet?: FileSet, options: Options}} ctx
*/
export function config(ctx) {
const repo = ctx.options.repository
function config(ctx) {
var repo = ctx.repository
var urlConfig = ctx.urlConfig
var info = {}
if (urlConfig) {
if (ctx.options.urlConfig) {
return
}
urlConfig = {
/** @type {UrlConfig} */
const urlConfig = {
prefix: '',
headingPrefix: '#',
lines: false,
hostname: null,
topAnchor: null
hostname: undefined,
topAnchor: undefined
}
if (repo) {
info = hostedGitInfo.fromUrl(repo)
}
const info = hostedGitInfo.fromUrl(repo)
if (info) {
if (info.type in viewPaths) {
urlConfig.prefix = '/' + info.path() + '/' + viewPaths[info.type] + '/'
}
if (info && info.type !== 'gist') {
if (info.type in viewPaths) {
urlConfig.prefix = '/' + info.path() + '/' + viewPaths[info.type] + '/'
}
if (info.type in headingPrefixes) {
urlConfig.headingPrefix = headingPrefixes[info.type]
}
if (info.type in headingPrefixes) {
urlConfig.headingPrefix = headingPrefixes[info.type]
}
if (info.type in lineLinks) {
urlConfig.lines = lineLinks[info.type]
}
if (info.type in lineLinks) {
urlConfig.lines = lineLinks[info.type]
}
if (info.type in topAnchors) {
urlConfig.topAnchor = topAnchors[info.type]
if (info.type in topAnchors) {
urlConfig.topAnchor = topAnchors[info.type]
}
urlConfig.hostname = info.domain
}
urlConfig.hostname = info.domain
}
ctx.urlConfig = urlConfig
ctx.options.urlConfig = urlConfig
}

@@ -1,7 +0,1 @@

'use strict'
module.exports = findRepo
function findRepo(ctx, next) {
next()
}
export async function findRepo() {}

@@ -1,14 +0,19 @@

'use strict'
/**
* @typedef {import('vfile').VFile} VFile
* @typedef {import('../index.js').Options} Options
*/
var path = require('path')
var exec = require('child_process').exec
import path from 'node:path'
import {promisify} from 'node:util'
import {exec as execCb} from 'node:child_process'
module.exports = findRepo
const exec = promisify(execCb)
function findRepo(ctx, next) {
var repo = ctx.repository
var file = ctx.file
var base = file.cwd
var actual = 0
var expected = 0
/**
* @param {{file: VFile, options: Options}} ctx
*/
export async function findRepo(ctx) {
const repo = ctx.options.repository
const file = ctx.file
let base = file.cwd

@@ -20,58 +25,23 @@ if (file.path) {

if (repo === null || repo === undefined) {
expected++
exec('git remote -v', {cwd: base}, onremote)
const {stdout} = await exec('git remote -v', {cwd: base})
const remote = stdout.match(/origin\t(.+?) \(fetch\)/)
ctx.options.repository = remote ? remote[1] : undefined
if (!ctx.options.repository) {
throw new Error('Could not find remote origin')
}
}
if (ctx.root === null || ctx.root === undefined) {
if (ctx.options.root === null || ctx.options.root === undefined) {
if (repo === null || repo === undefined) {
expected++
exec('git rev-parse --show-cdup', {cwd: base}, oncdup)
const {stdout} = await exec('git rev-parse --show-cdup', {cwd: base})
const out = stdout.trim()
ctx.options.root = out ? path.join(base, out) : base
} else {
ctx.root = ctx.file.cwd
ctx.options.root = ctx.file.cwd
}
} else {
ctx.root = path.resolve(file.cwd, ctx.root)
ctx.options.root = path.resolve(file.cwd, ctx.options.root)
}
if (actual === expected) {
next()
}
function onremote(error, stdout) {
var remote
if (error) {
expected = Infinity
return next(error)
}
remote = stdout.match(/origin\t(.+?) \(fetch\)/)
ctx.repository = remote ? remote[1] : null
if (!ctx.repository) {
expected = Infinity
return next(new Error('Could not find remote origin'))
}
if (++actual === expected) {
expected = Infinity
next()
}
}
function oncdup(error, stdout) {
var out
if (error) {
expected = Infinity
return next(error)
}
out = stdout.trim()
ctx.root = out ? path.join(base, out) : base
if (++actual === expected) {
next()
}
}
}

@@ -1,8 +0,6 @@

'use strict'
import {trough} from 'trough'
import {findRepo} from './find-repo.js'
import {config} from './config.js'
import {findReferences} from './find-references.js'
var trough = require('trough')
var findRepo = require('./find-repo')
var config = require('./config')
var find = require('./find')
module.exports = trough().use(findRepo).use(config).use(find)
export const find = trough().use(findRepo).use(config).use(findReferences)

@@ -1,14 +0,57 @@

'use strict'
/**
* @typedef {import('mdast').Root} Root
* @typedef {import('vfile').VFile} VFile
* @typedef {import('unified-engine').FileSet} FileSet
* @typedef {import('trough').Callback} Callback
*
* @typedef UrlConfig
* Hosted Git info
* @property {string|undefined} [hostname]
* Domain of URLs (example: `'github.com'`, `'bitbucket.org'`).
* @property {string|undefined} [prefix]
* Path prefix before files (example:
* `'/remarkjs/remark-validate-links/blob/'`,
* `'/remarkjs/remark-validate-links/src/'`).
* @property {string|undefined} [headingPrefix]
* Prefix of headings (example: `'#'`, `'#markdown-header-'`).
* @property {string|undefined} [topAnchor]
* Hash to top of readme (example: `#readme`).
* @property {boolean|undefined} [lines]
* Whether lines in files can be linked.
*
* @typedef Options
* Configuration.
* @property {string|false} [repository]
* URL to hosted Git.
* If `repository` is nullish, the Git origin remote is detected.
* 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`) can
* also be checked.
* If you’re not in a Git repository, you must pass `repository: false`
* explicitly.
* @property {string} [root]
* A `root` (`string?`) can also be passed, referencing the local Git root
* directory (the folder that contains `.git`).
* If both `root` and `repository` are nullish, the Git root is detected.
* If `root` is not given but `repository` is, `file.cwd` is used.
* @property {UrlConfig} [urlConfig]
* If your project is hosted on `github.com`, `gitlab.com`, or `bitbucket.org`,
* this plugin can automatically detect the url configuration.
* Otherwise, use `urlConfig` to specify this manually.
*/
var check = require('./check')
var find = require('./find')
var constants = require('./constants')
import {check} from './check/index.js'
import {find} from './find/index.js'
import {constants} from './constants.js'
module.exports = validateLinks
cliCompleter.pluginId = constants.sourceId
function validateLinks(options, fileSet) {
var settings = options || {}
/**
* Plugin to validate that Markdown links and images reference existing local
* files and headings.
*
* @type {import('unified').Plugin<[Options?, FileSet?]|[Options?]|void[], Root>}
*/
export default function remarkValidateLinks(options, fileSet) {
// Attach a `completer`.

@@ -19,24 +62,27 @@ if (fileSet) {

return transformer
// Find references and landmarks.
function transformer(tree, file, next) {
return (tree, file, next) => {
find.run(
Object.assign({}, settings, {tree: tree, file: file, fileSet: fileSet}),
done
{tree, file, fileSet, options: {...options}},
/** @type {Callback} */
(error) => {
if (error) {
next(error)
} else if (fileSet) {
next()
} else {
checkAll([file], next)
}
}
)
function done(error) {
if (error) {
next(error)
} else if (fileSet) {
next()
} else {
checkAll([file], next)
}
}
}
}
// Completer for the CLI (multiple files, supports parsing more files).
/**
* Completer for the CLI (multiple files, supports parsing more files).
*
* @param {FileSet} set
* @param {Callback} next
* @returns {void}
*/
function cliCompleter(set, next) {

@@ -46,7 +92,18 @@ checkAll(set.valueOf(), next)

/**
* Completer for the CLI (multiple files, supports parsing more files).
*
* @param {VFile[]} files
* @param {Callback} next
* @returns {void}
*/
function checkAll(files, next) {
// Check all references and landmarks.
check.run({files: files}, function (error) {
next(error)
})
check.run(
{files},
/** @type {Callback} */
(error) => {
next(error)
}
)
}
{
"name": "remark-validate-links",
"version": "10.0.4",
"version": "12.1.1",
"description": "remark plugin to validate links to headings and files",

@@ -35,2 +35,11 @@ "license": "MIT",

],
"sideEffects": false,
"type": "module",
"main": "index.js",
"types": "index.d.ts",
"files": [
"lib/",
"index.d.ts",
"index.js"
],
"browser": {

@@ -40,39 +49,40 @@ "./lib/check/check-files.js": "./lib/check/check-files.browser.js",

},
"files": [
"lib/",
"index.js"
],
"dependencies": {
"github-slugger": "^1.0.0",
"hosted-git-info": "^3.0.0",
"mdast-util-to-string": "^1.0.0",
"@types/mdast": "^3.0.0",
"github-slugger": "^2.0.0",
"hosted-git-info": "^6.0.0",
"mdast-util-to-string": "^3.2.0",
"propose": "0.0.5",
"to-vfile": "^6.0.0",
"trough": "^1.0.0",
"unist-util-visit": "^2.0.0"
"to-vfile": "^7.0.0",
"trough": "^2.0.0",
"unified": "^10.0.0",
"unified-engine": "^10.0.1",
"unist-util-visit": "^4.0.0",
"vfile": "^5.0.0"
},
"devDependencies": {
"nyc": "^15.0.0",
"@types/hast": "^2.0.0",
"@types/hosted-git-info": "^3.0.0",
"@types/rimraf": "^3.0.1",
"@types/tape": "^4.0.0",
"c8": "^7.0.0",
"prettier": "^2.0.0",
"remark": "^13.0.0",
"remark-cli": "^9.0.0",
"remark-preset-wooorm": "^8.0.0",
"remark": "^14.0.0",
"remark-cli": "^11.0.0",
"remark-preset-wooorm": "^9.0.0",
"rimraf": "^3.0.0",
"strip-ansi": "^6.0.0",
"strip-ansi": "^7.0.0",
"tape": "^5.0.0",
"vfile-sort": "^2.0.0",
"xo": "^0.38.0"
"type-coverage": "^2.0.0",
"typescript": "^5.0.0",
"vfile-sort": "^3.0.0",
"xo": "^0.54.0"
},
"scripts": {
"build": "rimraf \"lib/**/*.d.ts\" \"test/**/*.d.ts\" \"*.d.ts\" && tsc && type-coverage",
"format": "remark . -qfo --ignore-pattern test/ && prettier . -w --loglevel warn && xo --fix",
"test-api": "node test",
"test-coverage": "nyc --reporter lcov tape test/index.js",
"test": "npm run format && npm run test-coverage"
"test-api": "node --conditions development test/index.js",
"test-coverage": "c8 --check-coverage --branches 100 --functions 100 --lines 100 --statements 100 --reporter lcov npm run test-api",
"test": "npm run build && npm run format && npm run test-coverage"
},
"nyc": {
"check-coverage": true,
"lines": 100,
"functions": 100,
"branches": 100
},
"prettier": {

@@ -88,11 +98,4 @@ "tabWidth": 2,

"prettier": true,
"esnext": false,
"rules": {
"complexity": "off",
"guard-for-in": "off",
"unicorn/no-array-callback-reference": "off",
"unicorn/no-array-for-each": "off",
"unicorn/prefer-number-properties": "off",
"unicorn/prefer-optional-catch-binding": "off",
"unicorn/prefer-includes": "off"
"unicorn/prefer-logical-operator-over-ternary": "off"
}

@@ -104,3 +107,9 @@ },

]
},
"typeCoverage": {
"atLeast": 100,
"detail": true,
"strict": true,
"ignoreCatch": true
}
}

@@ -11,33 +11,24 @@ # remark-validate-links

[**remark**][remark] plugin to validate that Markdown links and images reference
existing local files and headings.
[**remark**][remark] plugin to check that markdown links and images point to
existing local files and headings in a Git repo.
For example, this document does not have a heading named `Hello`.
So if we’d link to it (`[welcome](#hello)`), we’d get a warning.
Links to headings in other markdown documents (`examples/foo.md#hello`) and
links to files (`license` or `index.js`) are also checked.
In addition, when there’s a link to a heading in another document
(`examples/foo.md#hello`), if that file exists but the heading does not, or if
that file does not exist, we’d also get a warning.
Linking to other files, such as `license` or `index.js` (when they exist) is
fine.
This plugin does not check external URLs (see
[`remark-lint-no-dead-urls`][no-dead-urls]) or undefined references
(see [`remark-lint-no-undefined-references`][no-undef-refs]).
## Note!
This plugin is ready for the new parser in remark
([`remarkjs/remark#536`](https://github.com/remarkjs/remark/pull/536)).
No change is needed: it works exactly the same now as it did before!
## Contents
* [What is this?](#what-is-this)
* [When should I use this?](#when-should-i-use-this)
* [Install](#install)
* [Use](#use)
* [CLI](#cli)
* [API](#api)
* [Configuration](#configuration)
* [API](#api)
* [`unified().use(remarkValidateLinks[, options])`](#unifieduseremarkvalidatelinks-options)
* [Examples](#examples)
* [Example: CLI](#example-cli)
* [Example: CLI in npm scripts](#example-cli-in-npm-scripts)
* [Integration](#integration)
* [Types](#types)
* [Compatibility](#compatibility)
* [Security](#security)

@@ -48,55 +39,50 @@ * [Related](#related)

## Install
## What is this?
[npm][]:
This package is a [unified][] ([remark][]) plugin to check local links in a Git
repo.
```sh
npm install remark-validate-links
```
**unified** is a project that transforms content with abstract syntax trees
(ASTs).
**remark** adds support for markdown to unified.
**mdast** is the markdown AST that remark uses.
This is a remark plugin that inspects mdast.
## Use
## When should I use this?
### CLI
This project is useful if you have a Git repo, such as this one, with docs in
markdown and links to headings and other files, and want to check whether
they’re correct.
Compared to other links checkers, this project can work offline (making it fast
en prone to fewer false positives), and is specifically made for local links in
Git repos.
This plugin does not check external URLs (see
[`remark-lint-no-dead-urls`][no-dead-urls]) or undefined references
(see [`remark-lint-no-undefined-references`][no-undef-refs]).
Use `remark-validate-links` together with [**remark**][remark]:
## Install
```bash
npm install --global remark-cli remark-validate-links
This package is [ESM only](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c).
In Node.js (version 14.14+, or 16.0+), install with [npm][]:
```sh
npm install remark-validate-links
```
Let’s say `readme.md` is this document, and `example.md` looks as follows:
In Deno with [`esm.sh`][esmsh]:
```markdown
# Hello
Read more [whoops, this does not exist](#world).
This doesn’t exist either [whoops!](readme.md#foo).
But this does exist: [license](license).
So does this: [README](readme.md#installation).
```js
import remarkValidateLinks from 'https://esm.sh/remark-validate-links@11'
```
Now, running `remark -u validate-links .` yields:
In browsers with [`esm.sh`][esmsh]:
```text
example.md
3:11-3:48 warning Link to unknown heading: `world` missing-heading remark-validate-links
5:27-5:51 warning Link to unknown heading in `readme.md`: `foo` missing-heading-in-file remark-validate-links
readme.md: no issues found
⚠ 2 warnings
```html
<script type="module">
import remarkValidateLinks from 'https://esm.sh/remark-validate-links@11?bundle'
</script>
```
> Note: passing a file over stdin(4) may not work as expected, because it is not
> known where the file originates from.
## Use
### API
> Note: The API checks links to headings and files.
> It does not check headings in other files.
> In a browser, only local links to headings are checked.
Say we have the following file, `example.md`:

@@ -114,3 +100,3 @@

Headings in `readme.md` are [checked](readme.md#nosuchheading).
Headings in `readme.md` are [checked](readme.md#no-such-heading).
And [missing files are reported](missing-example.js).

@@ -126,15 +112,18 @@

And our script, `example.js`, looks as follows:
And a module, `example.js`:
```js
var vfile = require('to-vfile')
var report = require('vfile-reporter')
var remark = require('remark')
var links = require('remark-validate-links')
import {read} from 'to-vfile'
import {remark} from 'remark'
import remarkValidateLinks from 'remark-validate-links'
remark()
.use(links)
.process(vfile.readSync('example.md'), function (err, file) {
console.error(report(err || file))
})
main()
async function main() {
const file = await remark()
.use(remarkValidateLinks)
.process(await read('example.md'))
console.log(reporter(file))
}
```

@@ -153,52 +142,51 @@

(Note that `readme.md#nosuchheading` is not warned about, because the API
does not check headings in other Markdown files).
(Note that `readme.md#no-such-heading` is not warned about, because the API does
not check headings in other Markdown files).
## Configuration
## API
This package exports no identifiers.
The default export is `remarkValidateLinks`.
### `unified().use(remarkValidateLinks[, options])`
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.
##### `options`
Typically, you don’t need to configure `remark-validate-links`, as it detects
local Git repositories.
If one is detected that references a known Git host (GitHub, GitLab,
or Bitbucket), some extra links can be checked.
If one is detected that does not reference a known Git host, local links still
work as expected.
If you’re not in a Git repository, you must pass `repository: false` explicitly.
You can pass a `repository` (`string?`, `false`).
###### `options.repository`
URL to hosted Git (`string?` or `false`).
If `repository` is nullish, the Git origin remote is detected.
If the repository resolves to something [npm understands][package-repository]
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`)
can also be checked.
If the repository resolves to something [npm understands][package-repository] as
a Git host such as GitHub, GitLab, or Bitbucket, then full URLs to that host
(say, `https://github.com/remarkjs/remark-validate-links/readme.md#install`) can
also be checked.
If you’re not in a Git repository, you must pass `repository: false`
explicitly.
```sh
remark --use 'validate-links=repository:"foo/bar"' example.md
```
###### `options.root`
For this to work, a `root` (`string?`) is also used, referencing the local Git
root directory (the place where `.git` is).
A `root` (`string?`) can also be passed, referencing the local Git root
directory (the folder that contains `.git`).
If both `root` and `repository` are nullish, the Git root is detected.
If `root` is not given but `repository` is, [`file.cwd`][cwd] is used.
You can define this repository in [configuration files][cli] too.
An example `.remarkrc` file could look as follows:
###### `options.urlConfig`
```json
{
"plugins": [
[
"validate-links",
{
"repository": "foo/bar"
}
]
]
}
```
If your project is hosted on `github.com`, `gitlab.com`, or `bitbucket.org`,
this plugin can automatically detect the url configuration.
Otherwise, use `urlConfig` to specify this manually.
For this repository (`remarkjs/remark-validate-links` on GitHub) `urlConfig`
looks as follows:
If you’re self-hosting a Git server, you can provide URL information directly,
as `urlConfig` (`Object`).
For this repository, `urlConfig` looks as follows:
```js

@@ -212,2 +200,4 @@ {

headingPrefix: '#',
// Hash to top of markdown documents:
topAnchor: '#readme',
// Whether lines in files can be linked:

@@ -229,2 +219,82 @@ lines: true

## Examples
### Example: CLI
It’s recommended to use `remark-validate-links` on the CLI with
[`remark-cli`][cli].
Install both with [npm][]:
```sh
npm install remark-cli remark-validate-links --save-dev
```
Let’s say we have a `readme.md` (this current document) and an `example.md`
with the following text:
```markdown
# Hello
Read more [whoops, this does not exist](#world).
This doesn’t exist either [whoops!](readme.md#foo).
But this does exist: [license](license).
So does this: [readme](readme.md#install).
```
Now, running `./node_modules/.bin/remark --use remark-validate-links .` yields:
```txt
example.md
3:11-3:48 warning Link to unknown heading: `world` missing-heading remark-validate-links
5:27-5:51 warning Link to unknown heading in `readme.md`: `foo` missing-heading-in-file remark-validate-links
readme.md: no issues found
⚠ 2 warnings
```
### Example: CLI in npm scripts
You can use `remark-validate-links` and [`remark-cli`][cli] in an npm script to
check and format markdown in your project.
Install both with [npm][]:
```sh
npm install remark-cli remark-validate-links --save-dev
```
Then, add a format script and configuration to `package.json`:
```js
{
// …
"scripts": {
// …
"format": "remark . --quiet --frail --output",
// …
},
"remarkConfig": {
"plugins": [
"remark-validate-links"
]
},
// …
}
```
> 💡 **Tip**: Add other tools such as prettier or ESLint to check and format
> other files.
>
> 💡 **Tip**: Run `./node_modules/.bin/remark --help` for help with
> `remark-cli`.
Now you check and format markdown in your project with:
```sh
npm run format
```
## Integration

@@ -235,9 +305,28 @@

* `node.data.hProperties.name` — Used by [`remark-html`][remark-html]
* `node.data.hProperties.name` — Used by
[`mdast-util-to-hast`][mdast-util-to-hast]
to create a `name` attribute, which anchors can link to
* `node.data.hProperties.id` — Used by [`remark-html`][remark-html]
* `node.data.hProperties.id` — Used by
[`mdast-util-to-hast`][mdast-util-to-hast]
to create an `id` attribute, which anchors can link to
* `node.data.id` — Used, in the future, by other tools to signal
* `node.data.id` — Used potentially in the future by other tools to signal
unique identifiers on nodes
## Types
This package is fully typed with [TypeScript][].
It exports an `Options` type, which specifies the interface of the accepted
options, and an `UrlConfig` type, which specifies the interface of its
corresponding option.
## Compatibility
Projects maintained by the unified collective are compatible with all maintained
versions of Node.js.
As of now, that is Node.js 14.14+, and 16.0+.
Our projects sometimes work with older versions, but this is not guaranteed.
This plugin works with `unified` version 6+, `remark` version 7+, and
`remark-cli` version 8+.
## Security

@@ -254,4 +343,6 @@

* [`remark-lint`][remark-lint] — Markdown code style linter
* [`remark-lint-no-dead-urls`][no-dead-urls] — Ensure external links are alive
* [`remark-lint`][remark-lint]
— markdown code style linter
* [`remark-lint-no-dead-urls`][no-dead-urls]
— check that external links are alive

@@ -302,2 +393,4 @@ ## Contribute

[esmsh]: https://esm.sh
[health]: https://github.com/remarkjs/.github

@@ -315,4 +408,8 @@

[unified]: https://github.com/unifiedjs/unified
[remark]: https://github.com/remarkjs/remark
[typescript]: https://www.typescriptlang.org
[cli]: https://github.com/remarkjs/remark/tree/HEAD/packages/remark-cli#readme

@@ -322,3 +419,3 @@

[remark-html]: https://github.com/remarkjs/remark-html
[mdast-util-to-hast]: https://github.com/syntax-tree/mdast-util-to-hast#notes

@@ -325,0 +422,0 @@ [no-dead-urls]: https://github.com/davidtheclark/remark-lint-no-dead-urls

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc