Skip to content

Commit

Permalink
Add support for using on API and in browser
Browse files Browse the repository at this point in the history
Previously, this plugin only worked on the CLI. This adds support
for usage on the API itself (invluding in browser environments).
  • Loading branch information
wooorm committed Aug 22, 2017
1 parent c88aed4 commit b1c646b
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 37 deletions.
78 changes: 49 additions & 29 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
'use strict';

var url = require('url');
var fs = require('fs');
var path = require('path');
var propose = require('propose');
var visit = require('unist-util-visit');
var definitions = require('mdast-util-definitions');
var hostedGitInfo = require('hosted-git-info');
var urljoin = require('urljoin');
var slug = require('remark-slug');
var xtend = require('xtend');
var xtend = require('xtend/mutable.js');

/* Optional Node dependencies. */
var fs;
var path;

try {
fs = require('fs');
path = require('path');
} catch (err) {}

module.exports = attacher;

var referenceId = 'remarkValidateLinksReferences';
var landmarkId = 'remarkValidateLinksLandmarks';
var sourceId = 'remark-validate-links';

completer.pluginId = sourceId;
cliCompleter.pluginId = sourceId;

var exists = fs.existsSync;
var parse = url.parse;

var viewPaths = {
Expand All @@ -39,14 +45,9 @@ function attacher(options, fileSet) {
var info;
var pack;

/* Throw when not on the CLI. */
if (!fileSet) {
throw new Error('remark-validate-links only works on the CLI');
}

/* Try to get the repo from `package.json` when not
* given. */
if (!repo) {
if (!repo && fs && fileSet) {
try {
pack = fileSet.files[0].cwd;
pack = JSON.parse(fs.readFileSync(path.resolve(pack, 'package.json')));
Expand All @@ -67,21 +68,34 @@ function attacher(options, fileSet) {
}
}

/* Attach a `completer`. */
fileSet.use(completer);

/* Attach `slug` and a plugin that adds our transformer after it. */
this.use(slug).use(subplugin);

/* Attach a `completer`. */
if (fileSet) {
fileSet.use(cliCompleter);
} else {
this.use(apiCompleter);
}

function subplugin() {
/* Expose transformer. */
return transformerFactory(fileSet, info);
}
}

/* Completer. */
function completer(set, done) {
/* Completer for the API (one file, only headings are checked). */
function apiCompleter() {
return transformer;
function transformer(tree, file) {
checkFactory(file.data[landmarkId])(file);
}
}

/* Completer for the CLI (multiple files, and support to add more). */
function cliCompleter(set, done) {
var exposed = {};
var check = checkFactory(exposed);

set.valueOf().forEach(expose);
set.valueOf().forEach(check);
Expand All @@ -92,10 +106,13 @@ function completer(set, done) {
var landmarks = file.data[landmarkId];

if (landmarks) {
exposed = xtend(exposed, landmarks);
xtend(exposed, landmarks);
}
}
}

function checkFactory(exposed) {
return check;
function check(file) {
/* istanbul ignore else - stdin */
if (file.path) {
Expand Down Expand Up @@ -125,13 +142,14 @@ function transformerFactory(fileSet, info) {
return;
}

references = gatherReferences(file, ast, info);
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
Expand Down Expand Up @@ -182,8 +200,8 @@ function validate(exposed, file) {
* 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) {
real = exists(reference);
if ((real === undefined || real === null) && !hash && fs) {
real = fs.existsSync(reference);
references[reference] = real;
}

Expand Down Expand Up @@ -214,16 +232,12 @@ function validate(exposed, file) {

/* Gather references: a map of file-paths references
* to be one or more nodes. */
function gatherReferences(file, tree, info) {
function gatherReferences(file, tree, info, fileSet) {
var cache = {};
var filePath = file.path;
var dirname = file.dirname;
var getDefinition;
var getDefinition = definitions(tree);
var prefix = '';
var headingPrefix = '#';

getDefinition = definitions(tree);

if (info && info.type in viewPaths) {
prefix = '/' + info.path() + '/' + viewPaths[info.type] + '/';
}
Expand Down Expand Up @@ -259,13 +273,18 @@ function gatherReferences(file, tree, info) {

uri = parse(link);

if (!fileSet && (uri.hostname || uri.pathname)) {
return;
}

if (!uri.hostname) {
/* Handle hashes, or relative files. */
if (!uri.pathname && uri.hash) {
link = filePath + uri.hash;
link = file.path + uri.hash;
uri = parse(link);
} else {
link = urljoin(dirname, link);
link = urljoin(file.dirname, link);

if (uri.hash) {
link += uri.hash;
}
Expand All @@ -275,7 +294,7 @@ function gatherReferences(file, tree, info) {

/* Handle full links. */
if (uri.hostname) {
if (!prefix) {
if (!prefix || !fileSet) {
return;
}

Expand Down Expand Up @@ -315,6 +334,7 @@ function gatherReferences(file, tree, info) {

if (hash) {
link = pathname + '#' + hash;

if (!cache[link]) {
cache[link] = [];
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"remark-preset-wooorm": "^3.0.0",
"strip-ansi": "^4.0.0",
"tape": "^4.0.0",
"to-vfile": "^2.1.2",
"xo": "^0.18.0"
},
"scripts": {
Expand Down
41 changes: 40 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,46 @@ readme.md: no issues found

## Programmatic

This plug-in is **not** available on the API of remark.
> Note: The API only checks links to headings. Other URLs are not checked.
Say we have the following file, `example.md`:

```markdown
# Alpha

This [exists](#alpha). This [exists][alpha] too.
This [one does not](#does-not).

# Bravo

This is [not checked](readme.md#bravo).

[alpha]: #alpha
```

And our script, `example.js`, looks as follows:

```javascript
var vfile = require('to-vfile');
var report = require('vfile-reporter');
var remark = require('remark');
var links = require('remark-validate-links');

remark()
.use(links)
.process(vfile.readSync('example.md'), function (err, file) {
console.error(report(err || file));
});
```

Now, running `node example` yields:

```markdown
example.md
4:6-4:31 warning Link to unknown heading: `does-not` remark-validate-links remark-validate-links

⚠ 1 warning
```

## Configuration

Expand Down
24 changes: 17 additions & 7 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ var fs = require('fs');
var path = require('path');
var test = require('tape');
var execa = require('execa');
var vfile = require('to-vfile');
var remark = require('remark');
var strip = require('strip-ansi');
var links = require('..');
Expand All @@ -21,13 +22,22 @@ var source = ' remark-validate-links remark-validate-links';
test('remark-validate-links', function (t) {
t.plan(12);

t.throws(
function () {
remark().use(links).freeze();
},
/Error: remark-validate-links only works on the CLI/,
'should throw an error when not on the CLI'
);
t.test('should work on the API', function (st) {
st.plan(1);

remark()
.use(links)
.process(vfile.readSync('github.md'), function (err, file) {
st.deepEqual(
[err].concat(file.messages.map(String)),
[
null,
'github.md:5:37-5:51: Link to unknown heading: `world`'
],
'should report messages'
);
});
});

t.test('should throw on unparsable git repositories', function (st) {
st.plan(1);
Expand Down

0 comments on commit b1c646b

Please sign in to comment.