Socket
Socket
Sign inDemoInstall

remark-validate-links

Package Overview
Dependencies
Maintainers
5
Versions
34
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 8.0.3 to 9.0.0

lib/check/check-files.browser.js

427

index.js
'use strict'
var url = require('url')
var propose = require('propose')
var visit = require('unist-util-visit')
var definitions = require('mdast-util-definitions')
var toString = require('mdast-util-to-string')
var hostedGitInfo = require('hosted-git-info')
var urljoin = require('urljoin')
var slugs = require('github-slugger')()
var xtend = require('xtend/mutable.js')
// Optional Node dependencies.
var fs
var path
try {
fs = require('fs')
path = require('path')
} catch (error) {}
module.exports = validateLinks
var referenceId = 'remarkValidateLinksReferences'
var landmarkId = 'remarkValidateLinksLandmarks'
var sourceId = 'remark-validate-links'
var headingRuleId = 'missing-heading'
var headingInFileRuleId = 'missing-heading-in-file'
var fileRuleId = 'missing-file'
cliCompleter.pluginId = sourceId
/* eslint-disable node/no-deprecated-api */
var parse = url.parse
var format = url.format
/* eslint-enable node/no-deprecated-api */
var viewPaths = {
github: 'blob',
gitlab: 'blob',
bitbucket: 'src'
}
var headingPrefixes = {
github: '#',
gitlab: '#',
bitbucket: '#markdown-header-'
}
var lineLinks = {
github: true,
gitlab: true
}
var lineExpression = /^#?l\d/i
function validateLinks(options, fileSet) {
var repo = (options || {}).repository
var info
var pack
// Try to get the repo from `package.json` when not given.
if (!repo && fs && fileSet) {
try {
pack = fileSet.files[0].cwd
pack = JSON.parse(fs.readFileSync(path.resolve(pack, 'package.json')))
} catch (error) {
pack = {}
}
repo = pack.repository ? pack.repository.url || pack.repository : ''
}
if (repo) {
info = hostedGitInfo.fromUrl(repo)
if (!info) {
throw new Error(
'remark-validate-links cannot parse `repository` (`' + repo + '`)'
)
} else if (info.domain === 'gist.github.com') {
throw new Error(
'remark-validate-links does not support gist repositories'
)
}
}
// Attach a plugin that adds our transformer after it.
this.use(subplugin)
// Attach a `completer`.
if (fileSet) {
fileSet.use(cliCompleter)
} else {
this.use(apiCompleter)
}
function subplugin() {
// Expose transformer.
return transformerFactory(fileSet, info)
}
}
// Completer for the API (one file, only headings are checked).
function apiCompleter() {
return apiTransform
}
function apiTransform(tree, file) {
checkFactory(file.data[landmarkId])(file)
}
// Completer for the CLI (multiple files, and support to add more).
function cliCompleter(set, done) {
var exposed = {}
set.valueOf().forEach(expose)
set.valueOf().forEach(checkFactory(exposed))
done()
function expose(file) {
var landmarks = file.data[landmarkId]
if (landmarks) {
xtend(exposed, landmarks)
}
}
}
function checkFactory(exposed) {
return check
function check(file) {
/* istanbul ignore else - stdin */
if (file.path) {
validate(exposed, file)
}
}
}
// Factory to create a transformer based on the given info and set.
function transformerFactory(fileSet, info) {
return transformer
// Transformer. Adds references files to the set.
function transformer(ast, file) {
var filePath = file.path
var space = file.data
var links = []
var landmarks = {}
var references
var current
var link
var pathname
/* istanbul ignore if - stdin */
if (!filePath) {
return
}
references = gatherReferences(file, ast, info, fileSet)
current = getPathname(filePath)
for (link in references) {
pathname = getPathname(link)
if (
fileSet &&
pathname !== current &&
getHash(link) &&
links.indexOf(pathname) === -1
) {
links.push(pathname)
fileSet.add(pathname)
}
}
landmarks[filePath] = true
slugs.reset()
visit(ast, mark)
space[referenceId] = references
space[landmarkId] = landmarks
function mark(node) {
var data = node.data || {}
var props = data.hProperties || {}
var id = props.name || props.id || data.id
if (!id && node.type === 'heading') {
id = slugs.slug(toString(node))
}
if (id) {
landmarks[filePath + '#' + id] = true
}
}
}
}
// Check if `file` references headings or files not in `exposed`.
function validate(exposed, file) {
var references = file.data[referenceId]
var filePath = file.path
var reference
var nodes
var real
var hash
var pathname
var warning
var suggestion
var ruleId
for (reference in references) {
nodes = references[reference]
real = exposed[reference]
hash = getHash(reference)
// Check if files without `hash` can be linked to. Because there’s no need
// to inspect those files for headings they are not added to remark. This
// is especially useful because they might be non-markdown files. Here we
// check if they exist.
if ((real === undefined || real === null) && !hash && fs) {
real = fs.existsSync(path.join(file.cwd, decodeURI(reference)))
references[reference] = real
}
if (!real) {
if (hash) {
pathname = getPathname(reference)
warning = 'Link to unknown heading'
ruleId = headingRuleId
if (pathname !== filePath) {
warning += ' in `' + pathname + '`'
ruleId = headingInFileRuleId
}
warning += ': `' + hash + '`'
} else {
warning = 'Link to unknown file: `' + decodeURI(reference) + '`'
ruleId = fileRuleId
}
suggestion = getClosest(reference, exposed)
if (suggestion) {
warning += '. Did you mean `' + suggestion + '`'
}
warnAll(file, nodes, warning, ruleId)
}
}
}
// Gather references: a map of file-paths references to be one or more nodes.
function gatherReferences(file, tree, info, fileSet) {
var cache = {}
var getDefinition = definitions(tree)
var prefix = ''
var headingPrefix = '#'
var lines
if (info && info.type in viewPaths) {
prefix = '/' + info.path() + '/' + viewPaths[info.type] + '/'
}
if (info && info.type in headingPrefixes) {
headingPrefix = headingPrefixes[info.type]
}
lines = info && info.type in lineLinks ? lineLinks[info.type] : false
visit(tree, ['link', 'image', 'linkReference', 'imageReference'], onresource)
return cache
// Handle resources.
function onresource(node) {
var link = node.url
var definition
var index
var uri
var pathname
var hash
// Handle references.
if (node.identifier) {
definition = getDefinition(node.identifier)
link = definition && definition.url
}
// Ignore definitions without url.
if (!link) {
return
}
uri = parse(link)
// Drop `?search`
uri.search = ''
link = format(uri)
if (!fileSet && (uri.hostname || uri.pathname)) {
return
}
if (!uri.hostname) {
if (lines && lineExpression.test(uri.hash)) {
uri.hash = ''
}
// Handle hashes, or relative files.
if (!uri.pathname && uri.hash) {
link = file.path + uri.hash
uri = parse(link)
} else {
link = urljoin(file.dirname, link)
if (uri.hash) {
link += uri.hash
}
uri = parse(link)
}
}
// Handle full links.
if (uri.hostname) {
if (!prefix || !fileSet) {
return
}
if (
uri.hostname !== info.domain ||
uri.pathname.slice(0, prefix.length) !== prefix
) {
return
}
link = uri.pathname.slice(prefix.length) + (uri.hash || '')
// Things get interesting here: branches: `foo/bar/baz` could be `baz` on
// the `foo/bar` branch, or, `baz` in the `bar` directory on the `foo`
// branch.
// Currently, we’re ignoring this and just not supporting branches.
link = link.slice(link.indexOf('/') + 1)
}
// Handle file links, or combinations of files and hashes.
index = link.indexOf(headingPrefix)
if (index === -1) {
pathname = link
hash = null
} else {
pathname = link.slice(0, index)
hash = link.slice(index + headingPrefix.length)
if (lines && lineExpression.test(hash)) {
hash = null
}
}
if (!cache[pathname]) {
cache[pathname] = []
}
cache[pathname].push(node)
if (hash) {
link = pathname + '#' + hash
if (!cache[link]) {
cache[link] = []
}
cache[link].push(node)
}
}
}
// Utility to warn `reason` for each node in `nodes` on `file`.
function warnAll(file, nodes, reason, ruleId) {
nodes.forEach(one)
function one(node) {
file.message(reason, node, [sourceId, ruleId].join(':'))
}
}
// Suggest a possible similar reference.
function getClosest(pathname, references) {
var hash = getHash(pathname)
var base = getPathname(pathname)
var dictionary = []
var reference
var subhash
var subbase
for (reference in references) {
subbase = getPathname(reference)
subhash = getHash(reference)
if (getPathname(reference) === base) {
if (subhash && hash) {
dictionary.push(subhash)
}
} else if (!subhash && !hash) {
dictionary.push(subbase)
}
}
return propose(hash ? hash : base, dictionary, {threshold: 0.7})
}
// Get the `hash` of `uri`, if applicable.
function getHash(uri) {
var hash = parse(uri).hash
return hash ? hash.slice(1) : null
}
// Get the `pathname` of `uri`, if applicable.
function getPathname(uri) {
return parse(uri).pathname
}
module.exports = require('./lib')
{
"name": "remark-validate-links",
"version": "8.0.3",
"version": "9.0.0",
"description": "remark plugin to validate links to headings and files",

@@ -30,3 +30,8 @@ "license": "MIT",

],
"browser": {
"lib/check/check-files.js": "lib/check/check-files.browser.js",
"lib/find/find-repo.js": "lib/find/find-repo.browser.js"
},
"files": [
"lib/",
"index.js"

@@ -37,11 +42,10 @@ ],

"hosted-git-info": "^2.5.0",
"mdast-util-definitions": "^1.0.0",
"mdast-util-to-string": "^1.0.4",
"propose": "0.0.5",
"trough": "^1.0.0",
"unist-util-visit": "^1.0.0",
"urljoin": "^0.1.5",
"xtend": "^4.0.1"
"xtend": "^4.0.0"
},
"devDependencies": {
"execa": "^1.0.0",
"execa": "^2.0.0",
"nyc": "^14.0.0",

@@ -52,2 +56,3 @@ "prettier": "^1.0.0",

"remark-preset-wooorm": "^5.0.0",
"rimraf": "^2.0.0",
"strip-ansi": "^5.0.0",

@@ -83,7 +88,3 @@ "tape": "^4.0.0",

"rules": {
"no-eq-null": "off",
"eqeqeq": "off",
"guard-for-in": "off",
"max-lines": "off",
"complexity": "off"
"guard-for-in": "off"
}

@@ -90,0 +91,0 @@ },

@@ -19,3 +19,3 @@ # remark-validate-links

In addition, when I link to a heading in another document
In addition, when there’s a link to a heading in another document
(`examples/foo.md#hello`), if this file exists but the heading does not, or if

@@ -83,6 +83,10 @@ the file does not exist, this plugin will also warn.

> Note: passing a file over stdin(4) may not work as expected, because it is not
> known where the file originates from.
### API
> Note: The API only checks links to headings.
> Other URLs are not checked.
> 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.

@@ -94,10 +98,13 @@ Say we have the following file, `example.md`:

This [exists](#alpha). This [exists][alpha] too.
This [exists](#alpha).
This [one does not](#does-not).
References and definitions are [checked][alpha] [too][charlie].
# Bravo
This is [not checked](readme.md#bravo).
Headings in `readme.md` are [not checked](readme.md#bravo).
But [missing files are reported](missing-example.js).
[alpha]: #alpha
[charlie]: #charlie
```

@@ -124,5 +131,7 @@

example.md
4:6-4:31 warning Link to unknown heading: `does-not` remark-validate-links remark-validate-links
4:6-4:31 warning Link to unknown heading: `does-not` missing-heading remark-validate-links
10:5-10:53 warning Link to unknown file: `missing-example.js` missing-file remark-validate-links
13:1-13:20 warning Link to unknown heading: `charlie` missing-heading remark-validate-links
⚠ 1 warning
⚠ 3 warnings
```

@@ -132,7 +141,17 @@

You can pass a `repository`, containing anything `package.json`s
[`repository`][package-repository] can handle.
If this is not given, `remark-validate-links` will try the `package.json` in
the current working directory.
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, 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`).
If `repository` is nully, 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.
```sh

@@ -142,6 +161,6 @@ remark --use 'validate-links=repository:"foo/bar"' example.md

When a repository is given or detected (supporting GitHub, GitLab, and
Bitbucket), links to the files are normalized to the file system.
For example, `https://github.com/foo/bar/blob/master/example.md` becomes
`example.md`.
For this to work, a `root` (`string?`) is also used, referencing the local Git
root directory (the place where `.git` is).
If both `root` and `repository` are nully, the Git root is detected.
If `root` is not given but `repository` is, [`file.cwd`][cwd] is used.

@@ -164,2 +183,31 @@ You can define this repository in [configuration files][cli] too.

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
{
// Domain of URLs:
hostname: 'github.com',
// Path prefix before files:
prefix: '/remarkjs/remark-validate-links/blob/',
// Prefix of headings:
headingPrefix: '#',
// 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
}
```
## Integration

@@ -249,1 +297,3 @@

[package-repository]: https://docs.npmjs.com/files/package.json#repository
[cwd]: https://github.com/vfile/vfile#vfilecwd
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