diff --git a/index.js b/index.js index 36417ce..26f5651 100644 --- a/index.js +++ b/index.js @@ -6,20 +6,37 @@ var path = require('path'); var propose = require('propose'); var visit = require('unist-util-visit'); var definitions = require('mdast-util-definitions'); -var gh = require('github-url-to-object'); +var hostedGitInfo = require('hosted-git-info'); var urljoin = require('urljoin'); var slug = require('remark-slug'); var xtend = require('xtend'); module.exports = attacher; -completer.pluginId = 'remark-validate-links'; +var referenceId = 'remarkValidateLinksReferences'; +var landmarkId = 'remarkValidateLinksLandmarks'; +var sourceId = 'remark-validate-links'; + +completer.pluginId = sourceId; var exists = fs.existsSync; var parse = url.parse; +var viewPaths = { + github: 'blob', + gitlab: 'blob', + bitbucket: 'src' +}; + +var headingPrefixes = { + github: '#', + gitlab: '#', + bitbucket: '#markdown-header-' +}; + function attacher(options, fileSet) { var repo = (options || {}).repository; + var info; var pack; /* Throw when not on the CLI. */ @@ -40,17 +57,25 @@ function attacher(options, fileSet) { repo = pack.repository ? pack.repository.url || pack.repository : ''; } - repo = repo ? gh(repo) : {}; + 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 `completer`. */ fileSet.use(completer); - /* Attach `slug`. */ + /* Attach `slug` and a plugin that adds our transformer after it. */ this.use(slug).use(subplugin); function subplugin() { /* Expose transformer. */ - return transformerFactory({user: repo.user, repo: repo.repo}, fileSet); + return transformerFactory(fileSet, info); } } @@ -58,27 +83,30 @@ function attacher(options, fileSet) { function completer(set, done) { var exposed = {}; - set.valueOf().forEach(function (file) { - var landmarks = file.data.remarkValidateLinksLandmarks; + set.valueOf().forEach(expose); + set.valueOf().forEach(check); + + done(); + + function expose(file) { + var landmarks = file.data[landmarkId]; if (landmarks) { exposed = xtend(exposed, landmarks); } - }); + } - set.valueOf().forEach(function (file) { + function check(file) { /* istanbul ignore else - stdin */ if (file.path) { validate(exposed, file); } - }); - - done(); + } } /* Factory to create a transformer based on the given - * project and set. */ -function transformerFactory(project, fileSet) { + * info and set. */ +function transformerFactory(fileSet, info) { return transformer; /* Transformer. Adds references files to the set. */ @@ -97,7 +125,7 @@ function transformerFactory(project, fileSet) { return; } - references = gatherReferences(file, ast, project); + references = gatherReferences(file, ast, info); current = getPathname(filePath); for (link in references) { @@ -115,7 +143,12 @@ function transformerFactory(project, fileSet) { landmarks[filePath] = true; - visit(ast, function (node) { + visit(ast, mark); + + space[referenceId] = references; + space[landmarkId] = landmarks; + + function mark(node) { var data = node.data || {}; var attrs = data.hProperties || data.htmlAttributes || {}; var id = attrs.name || attrs.id || data.id; @@ -123,18 +156,13 @@ function transformerFactory(project, fileSet) { if (id) { landmarks[filePath + '#' + id] = true; } - }); - - space.remarkValidateLinksReferences = references; - space.remarkValidateLinksLandmarks = landmarks; + } } } -/* Check if `file` references headings or files not in - * `exposed`. If `project` is given, normalizes GitHub blob - * URLs. */ +/* Check if `file` references headings or files not in `exposed`. */ function validate(exposed, file) { - var references = file.data.remarkValidateLinksReferences; + var references = file.data[referenceId]; var filePath = file.path; var reference; var nodes; @@ -186,19 +214,29 @@ function validate(exposed, file) { /* Gather references: a map of file-paths references * to be one or more nodes. */ -function gatherReferences(file, tree, project) { +function gatherReferences(file, tree, info) { var cache = {}; var filePath = file.path; var dirname = file.dirname; var getDefinition; var prefix = ''; + var headingPrefix = '#'; getDefinition = definitions(tree); - if (project.user && project.repo) { - prefix = '/' + project.user + '/' + project.repo + '/blob/'; + if (info && info.type in viewPaths) { + prefix = '/' + info.path() + '/' + viewPaths[info.type] + '/'; } + if (info && info.type in headingPrefixes) { + headingPrefix = headingPrefixes[info.type]; + } + + visit(tree, 'link', onlink); + visit(tree, 'linkReference', onlink); + + return cache; + /* Handle new links. */ function onlink(node) { var link = node.url; @@ -235,18 +273,13 @@ function gatherReferences(file, tree, project) { } } - /* Handle full links. - * Only works with GitHub. - */ + /* Handle full links. */ if (uri.hostname) { if (!prefix) { return; } - if ( - uri.hostname !== 'github.com' || - uri.pathname.slice(0, prefix.length) !== prefix - ) { + if (uri.hostname !== info.domain || uri.pathname.slice(0, prefix.length) !== prefix) { return; } @@ -260,20 +293,18 @@ function gatherReferences(file, tree, project) { * Currently, I’m ignoring this and just not * supporting branches. */ link = link.slice(link.indexOf('/') + 1); - - uri = parse(link); } /* Handle file links, or combinations of files * and hashes. */ - index = link.indexOf('#'); + index = link.indexOf(headingPrefix); if (index === -1) { pathname = link; hash = null; } else { pathname = link.slice(0, index); - hash = link.slice(index + 1); + hash = link.slice(index + headingPrefix.length); } if (!cache[pathname]) { @@ -283,6 +314,7 @@ function gatherReferences(file, tree, project) { cache[pathname].push(node); if (hash) { + link = pathname + '#' + hash; if (!cache[link]) { cache[link] = []; } @@ -290,21 +322,18 @@ function gatherReferences(file, tree, project) { cache[link].push(node); } } - - visit(tree, 'link', onlink); - visit(tree, 'linkReference', onlink); - - return cache; } /* Utilitity to warn `reason` for each node in `nodes`, * on `file`. */ function warnAll(file, nodes, reason) { - nodes.forEach(function (node) { + nodes.forEach(one); + + function one(node) { var message = file.message(reason, node); - message.source = 'remark-validate-links'; - message.ruleId = 'remark-validate-links'; - }); + message.source = sourceId; + message.ruleId = sourceId; + } } /* Suggest a possible similar reference. */ diff --git a/package.json b/package.json index 5af1474..8c6834f 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "index.js" ], "dependencies": { - "github-url-to-object": "^4.0.0", + "hosted-git-info": "^2.5.0", "mdast-util-definitions": "^1.0.0", "propose": "0.0.5", "remark-slug": "^4.2.1", diff --git a/readme.md b/readme.md index 64b4238..0995d73 100644 --- a/readme.md +++ b/readme.md @@ -81,27 +81,31 @@ directory. remark --use 'validate-links=repository:"foo/bar"' example.md ``` -When a repository is given or detected, links to GitHub are normalized -to the file-system. For example, -`https://github.com/foo/bar/blob/master/example.md` becomes `example.md`. +When a repository is given or detected (supporting GitHub, GitLab, and +Bitbucket), links to the only files are normalized to the file-system. +For example, `https://github.com/foo/bar/blob/master/example.md` becomes +`example.md`. You can define this repository in [configuration files][cli] too. An example `.remarkrc` file could look as follows: ```json { - "plugins": { - "validate-links": { - "repository": "foo/bar" - } - } + "plugins": [ + [ + "validate-links", + { + "repository": "foo/bar" + } + ] + ] } ``` ## Integration -**remark-validate-links** can detect anchors on nodes through -several properties on nodes: +`remark-validate-links` can detect anchors on nodes through several properties +on nodes: * `node.data.hProperties.name` — Used by [`remark-html`][remark-html] to create a `name` attribute, which anchors can link to diff --git a/test/fixtures/bitbucket.md b/test/fixtures/bitbucket.md new file mode 100644 index 0000000..77809e5 --- /dev/null +++ b/test/fixtures/bitbucket.md @@ -0,0 +1,63 @@ +# Hello + +This is a valid relative heading [link](#markdown-header-hello). + +This is an invalid relative heading [link](#markdown-header-world). + +## Files + +This is a valid relative file [link](https://bitbucket.org/wooorm/test/src/master/examples/bitbucket.md). + +So is this [link](https://bitbucket.org/wooorm/test/src/foo-bar/examples/bitbucket.md). + +And this [link](./examples/bitbucket.md). + +And this [link](examples/bitbucket.md). + +This is a valid external [file](../index.js). + +This is an invalid relative file [link](https://bitbucket.org/wooorm/test/src/master/examples/world.md). + +So is this [link](https://bitbucket.org/wooorm/test/src/foo-bar/examples/world.md). + +And this [link](./examples/world.md). + +And this [link](examples/world.md). + +## Combination + +Valid: [a](./examples/bitbucket.md#markdown-header-hello). + +Valid: [b](examples/bitbucket.md#markdown-header-hello). + +Valid: [c](https://bitbucket.org/wooorm/test/src/master/examples/bitbucket.md#markdown-header-hello). + +Valid: [d](https://bitbucket.org/wooorm/test/src/foo-bar/examples/bitbucket.md#markdown-header-hello). + +Invalid: [e](./examples/bitbucket.md#markdown-header-world). + +Invalid: [f](examples/bitbucket.md#markdown-header-world). + +Invalid: [g](https://bitbucket.org/wooorm/test/src/master/examples/bitbucket.md#markdown-header-world). + +Invalid: [h](https://bitbucket.org/wooorm/test/src/foo-bar/examples/bitbucket.md#markdown-header-world). + +Invalid: [i](./examples/world.md#markdown-header-hello). + +Invalid: [j](examples/world.md#markdown-header-hello). + +Invalid: [k](https://bitbucket.org/wooorm/test/src/master/examples/world.md#markdown-header-hello). + +Invalid: [l](https://bitbucket.org/wooorm/test/src/foo-bar/examples/world.md#markdown-header-hello). + +## External + +These are all invalid, because they do not link to Bitbucket. + +Valid: [a](irc://foo). + +Valid: [b](http://example.com). + +Valid: [b](http://example.com/foo/bar/baz). + +Valid: [b](http://github.com/wooorm/test/blob/foo-bar/examples/world.md#markdown-header-hello). diff --git a/test/fixtures/examples/bitbucket.md b/test/fixtures/examples/bitbucket.md new file mode 100644 index 0000000..b98960c --- /dev/null +++ b/test/fixtures/examples/bitbucket.md @@ -0,0 +1,39 @@ +# Hello + +This is a valid relative heading [link](#markdown-header-hello). + +This is an invalid relative heading [link](#markdown-header-world). + +## Files + +This is a valid relative file [link](https://bitbucket.org/wooorm/test/src/master/bitbucket.md). + +So is this [link](https://bitbucket.org/wooorm/test/src/foo-bar/bitbucket.md). + +And this [link](../bitbucket.md). + +This is an invalid relative file [link](https://bitbucket.org/wooorm/test/src/master/world.md). + +So is this [link](https://bitbucket.org/wooorm/test/src/foo-bar/world.md). + +And this [link](../world.md). + +## Combination + +Valid: [a](../bitbucket.md#markdown-header-hello). + +Valid: [b](https://bitbucket.org/wooorm/test/src/master/bitbucket.md#markdown-header-hello). + +Valid: [c](https://bitbucket.org/wooorm/test/src/foo-bar/bitbucket.md#markdown-header-hello). + +Invalid: [d](../bitbucket.md#markdown-header-world). + +Invalid: [e](https://bitbucket.org/wooorm/test/src/master/bitbucket.md#markdown-header-world). + +Invalid: [f](https://bitbucket.org/wooorm/test/src/foo-bar/bitbucket.md#markdown-header-world). + +Invalid: [g](../world.md#markdown-header-hello). + +Invalid: [h](https://bitbucket.org/wooorm/test/src/master/world.md#markdown-header-hello). + +Invalid: [i](https://bitbucket.org/wooorm/test/src/foo-bar/world.md#markdown-header-hello). diff --git a/test/fixtures/examples/example.md b/test/fixtures/examples/github.md similarity index 55% rename from test/fixtures/examples/example.md rename to test/fixtures/examples/github.md index e8edf05..d8623e4 100644 --- a/test/fixtures/examples/example.md +++ b/test/fixtures/examples/github.md @@ -6,11 +6,11 @@ This is an invalid relative heading [link](#world). ## Files -This is a valid relative file [link](https://github.com/wooorm/test/blob/master/example.md). +This is a valid relative file [link](https://github.com/wooorm/test/blob/master/github.md). -So is this [link](https://github.com/wooorm/test/blob/foo-bar/example.md). +So is this [link](https://github.com/wooorm/test/blob/foo-bar/github.md). -And this [link](../example.md). +And this [link](../github.md). This is an invalid relative file [link](https://github.com/wooorm/test/blob/master/world.md). @@ -20,17 +20,17 @@ And this [link](../world.md). ## Combination -Valid: [a](../example.md#hello). +Valid: [a](../github.md#hello). -Valid: [b](https://github.com/wooorm/test/blob/master/example.md#hello). +Valid: [b](https://github.com/wooorm/test/blob/master/github.md#hello). -Valid: [c](https://github.com/wooorm/test/blob/foo-bar/example.md#hello). +Valid: [c](https://github.com/wooorm/test/blob/foo-bar/github.md#hello). -Invalid: [d](../example.md#world). +Invalid: [d](../github.md#world). -Invalid: [e](https://github.com/wooorm/test/blob/master/example.md#world). +Invalid: [e](https://github.com/wooorm/test/blob/master/github.md#world). -Invalid: [f](https://github.com/wooorm/test/blob/foo-bar/example.md#world). +Invalid: [f](https://github.com/wooorm/test/blob/foo-bar/github.md#world). Invalid: [g](../world.md#hello). diff --git a/test/fixtures/examples/gitlab.md b/test/fixtures/examples/gitlab.md new file mode 100644 index 0000000..16a2e1b --- /dev/null +++ b/test/fixtures/examples/gitlab.md @@ -0,0 +1,39 @@ +# Hello + +This is a valid relative heading [link](#hello). + +This is an invalid relative heading [link](#world). + +## Files + +This is a valid relative file [link](https://gitlab.com/wooorm/test/blob/master/gitlab.md). + +So is this [link](https://gitlab.com/wooorm/test/blob/foo-bar/gitlab.md). + +And this [link](../gitlab.md). + +This is an invalid relative file [link](https://gitlab.com/wooorm/test/blob/master/world.md). + +So is this [link](https://gitlab.com/wooorm/test/blob/foo-bar/world.md). + +And this [link](../world.md). + +## Combination + +Valid: [a](../gitlab.md#hello). + +Valid: [b](https://gitlab.com/wooorm/test/blob/master/gitlab.md#hello). + +Valid: [c](https://gitlab.com/wooorm/test/blob/foo-bar/gitlab.md#hello). + +Invalid: [d](../gitlab.md#world). + +Invalid: [e](https://gitlab.com/wooorm/test/blob/master/gitlab.md#world). + +Invalid: [f](https://gitlab.com/wooorm/test/blob/foo-bar/gitlab.md#world). + +Invalid: [g](../world.md#hello). + +Invalid: [h](https://gitlab.com/wooorm/test/blob/master/world.md#hello). + +Invalid: [i](https://gitlab.com/wooorm/test/blob/foo-bar/world.md#hello). diff --git a/test/fixtures/example.md b/test/fixtures/github.md similarity index 74% rename from test/fixtures/example.md rename to test/fixtures/github.md index d40da34..5c56074 100644 --- a/test/fixtures/example.md +++ b/test/fixtures/github.md @@ -6,13 +6,13 @@ This is an invalid relative heading [link](#world). ## Files -This is a valid relative file [link](https://github.com/wooorm/test/blob/master/examples/example.md). +This is a valid relative file [link](https://github.com/wooorm/test/blob/master/examples/github.md). -So is this [link](https://github.com/wooorm/test/blob/foo-bar/examples/example.md). +So is this [link](https://github.com/wooorm/test/blob/foo-bar/examples/github.md). -And this [link](./examples/example.md). +And this [link](./examples/github.md). -And this [link](examples/example.md). +And this [link](examples/github.md). This is a valid external [file](../index.js). @@ -26,21 +26,21 @@ And this [link](examples/world.md). ## Combination -Valid: [a](./examples/example.md#hello). +Valid: [a](./examples/github.md#hello). -Valid: [b](examples/example.md#hello). +Valid: [b](examples/github.md#hello). -Valid: [c](https://github.com/wooorm/test/blob/master/examples/example.md#hello). +Valid: [c](https://github.com/wooorm/test/blob/master/examples/github.md#hello). -Valid: [d](https://github.com/wooorm/test/blob/foo-bar/examples/example.md#hello). +Valid: [d](https://github.com/wooorm/test/blob/foo-bar/examples/github.md#hello). -Invalid: [e](./examples/example.md#world). +Invalid: [e](./examples/github.md#world). -Invalid: [f](examples/example.md#world). +Invalid: [f](examples/github.md#world). -Invalid: [g](https://github.com/wooorm/test/blob/master/examples/example.md#world). +Invalid: [g](https://github.com/wooorm/test/blob/master/examples/github.md#world). -Invalid: [h](https://github.com/wooorm/test/blob/foo-bar/examples/example.md#world). +Invalid: [h](https://github.com/wooorm/test/blob/foo-bar/examples/github.md#world). Invalid: [i](./examples/world.md#hello). diff --git a/test/fixtures/gitlab.md b/test/fixtures/gitlab.md new file mode 100644 index 0000000..b4bcb46 --- /dev/null +++ b/test/fixtures/gitlab.md @@ -0,0 +1,63 @@ +# Hello + +This is a valid relative heading [link](#hello). + +This is an invalid relative heading [link](#world). + +## Files + +This is a valid relative file [link](https://gitlab.com/wooorm/test/blob/master/examples/gitlab.md). + +So is this [link](https://gitlab.com/wooorm/test/blob/foo-bar/examples/gitlab.md). + +And this [link](./examples/gitlab.md). + +And this [link](examples/gitlab.md). + +This is a valid external [file](../index.js). + +This is an invalid relative file [link](https://gitlab.com/wooorm/test/blob/master/examples/world.md). + +So is this [link](https://gitlab.com/wooorm/test/blob/foo-bar/examples/world.md). + +And this [link](./examples/world.md). + +And this [link](examples/world.md). + +## Combination + +Valid: [a](./examples/gitlab.md#hello). + +Valid: [b](examples/gitlab.md#hello). + +Valid: [c](https://gitlab.com/wooorm/test/blob/master/examples/gitlab.md#hello). + +Valid: [d](https://gitlab.com/wooorm/test/blob/foo-bar/examples/gitlab.md#hello). + +Invalid: [e](./examples/gitlab.md#world). + +Invalid: [f](examples/gitlab.md#world). + +Invalid: [g](https://gitlab.com/wooorm/test/blob/master/examples/gitlab.md#world). + +Invalid: [h](https://gitlab.com/wooorm/test/blob/foo-bar/examples/gitlab.md#world). + +Invalid: [i](./examples/world.md#hello). + +Invalid: [j](examples/world.md#hello). + +Invalid: [k](https://gitlab.com/wooorm/test/blob/master/examples/world.md#hello). + +Invalid: [l](https://gitlab.com/wooorm/test/blob/foo-bar/examples/world.md#hello). + +## External + +These are all invalid, because they do not link to GitLab. + +Valid: [a](irc://foo). + +Valid: [b](http://example.com). + +Valid: [b](http://example.com/foo/bar/baz). + +Valid: [b](http://bitbucket.com/wooorm/test/blob/foo-bar/examples/world.md#hello). diff --git a/test/fixtures/suggestions.md b/test/fixtures/suggestions.md index 23b2068..66fc624 100644 --- a/test/fixtures/suggestions.md +++ b/test/fixtures/suggestions.md @@ -4,8 +4,8 @@ This should suggest: [link](#helloo). ## Files -So should this: [k](example.md#fiiiles). +So should this: [k](github.md#fiiiles). -## +## An empty header. diff --git a/test/index.js b/test/index.js index 1e0fc31..f19b4bb 100644 --- a/test/index.js +++ b/test/index.js @@ -19,7 +19,7 @@ var bin = path.join('..', '..', 'node_modules', '.bin', 'remark'); var source = ' remark-validate-links remark-validate-links'; test('remark-validate-links', function (t) { - t.plan(8); + t.plan(12); t.throws( function () { @@ -29,18 +29,65 @@ test('remark-validate-links', function (t) { 'should throw an error when not on the CLI' ); + t.test('should throw on unparsable git repositories', function (st) { + st.plan(1); + + execa(bin, [ + '--no-config', + '--no-ignore', + '--use', + '../..=repository:"invalid:shortcode"', + 'definitions.md' + ]).then(function () { + st.fail('should not be successful'); + }, function (err) { + st.equal( + strip(err.stderr).split('\n').slice(0, 2).join('\n'), + [ + 'definitions.md', + ' 1:1 error Error: remark-validate-links cannot parse `repository` (`invalid:shortcode`)' + ].join('\n'), + 'should report an error' + ); + }); + }); + + t.test('should throw on gist repositories', function (st) { + st.plan(1); + + execa(bin, [ + '--no-config', + '--no-ignore', + '--use', + '../..=repository:"gist:wooorm/8504606"', + 'definitions.md' + ]).then(function () { + st.fail('should not be successful'); + }, function (err) { + st.equal( + strip(err.stderr).split('\n').slice(0, 2).join('\n'), + [ + 'definitions.md', + ' 1:1 error Error: remark-validate-links does not support gist repositories' + ].join('\n'), + 'should report an error' + ); + }); + }); + t.test('should ignore unfound files', function (st) { - st.plan(2); + st.plan(1); - execa.stderr(bin, [ + execa(bin, [ '--no-config', '--no-ignore', '--use', - '../../index', + '../..', 'definitions.md', 'FOOOO' - ]).catch(function (err) { - st.equal(err.code, 1, 'should exit with `1`'); + ]).then(function () { + st.fail('should not be successful'); + }, function (err) { st.equal( strip(err.stderr), [ @@ -53,7 +100,7 @@ test('remark-validate-links', function (t) { '2 messages (✖ 1 error, ⚠ 1 warning)', '' ].join('\n'), - 'should report' + 'should report an error' ); }); }); @@ -61,85 +108,85 @@ test('remark-validate-links', function (t) { t.test('should work when not all files are given', function (st) { st.plan(1); - execa.stderr(bin, [ + execa(bin, [ '--no-config', '--no-ignore', '--use', - '../../index', - 'example.md' - ]).then(function (stderr) { + '../..', + 'github.md' + ]).then(function (result) { st.equal( - strip(stderr), + strip(result.stderr), [ - 'example.md', - ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, - ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, - ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, - ' 37:10-37:42 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 39:10-39:40 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 45:10-45:40 warning Link to unknown file: `examples/world.md` ' + source, - ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 47:10-47:38 warning Link to unknown file: `examples/world.md` ' + source, - ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + 'github.md', + ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, + ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, + ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, + ' 37:10-37:41 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 39:10-39:39 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 45:10-45:40 warning Link to unknown file: `examples/world.md` ' + source, + ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 47:10-47:38 warning Link to unknown file: `examples/world.md` ' + source, + ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, '', '⚠ 9 warnings' ].join('\n'), 'should report' ); - }); + }, st.error); }); t.test('should work when all files are given', function (st) { st.plan(1); - execa.stderr(bin, [ + execa(bin, [ '--no-config', '--no-ignore', '--use', - '../../index', - 'example.md', - 'examples/example.md' - ]).then(function (stderr) { + '../..', + 'github.md', + 'examples/github.md' + ]).then(function (result) { st.equal( - strip(stderr), + strip(result.stderr), [ - 'example.md', - ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, - ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, - ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, - ' 37:10-37:42 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 39:10-39:40 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 45:10-45:40 warning Link to unknown file: `examples/world.md` ' + source, - ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 47:10-47:38 warning Link to unknown file: `examples/world.md` ' + source, - ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + 'examples/github.md', + ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, + ' 19:10-19:29 warning Link to unknown file: `world.md` ' + source, + ' 29:10-29:33 warning Link to unknown heading in `github.md`: `world` ' + source, + ' 35:10-35:32 warning Link to unknown file: `world.md` ' + source, + ' 35:10-35:32 warning Link to unknown heading in `world.md`: `hello` ' + source, '', - 'examples/example.md', - ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, - ' 19:10-19:29 warning Link to unknown file: `world.md` ' + source, - ' 29:10-29:34 warning Link to unknown heading in `example.md`: `world` ' + source, - ' 35:10-35:32 warning Link to unknown file: `world.md` ' + source, - ' 35:10-35:32 warning Link to unknown heading in `world.md`: `hello` ' + source, + 'github.md', + ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, + ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, + ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, + ' 37:10-37:41 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 39:10-39:39 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 45:10-45:40 warning Link to unknown file: `examples/world.md` ' + source, + ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 47:10-47:38 warning Link to unknown file: `examples/world.md` ' + source, + ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, '', '⚠ 14 warnings' ].join('\n'), 'should report' ); - }); + }, st.error); }); t.test('should work with definitions', function (st) { st.plan(1); - execa.stderr(bin, [ + execa(bin, [ '--no-config', '--no-ignore', '--use', - '../../index', + '../..', 'definitions.md' - ]).then(function (stderr) { + ]).then(function (result) { st.equal( - strip(stderr), + strip(result.stderr), [ 'definitions.md', ' 5:12-5:21 warning Link to unknown heading: `world`' + source, @@ -148,62 +195,62 @@ test('remark-validate-links', function (t) { ].join('\n'), 'should report' ); - }); + }, st.error); }); t.test('should work on GitHub URLs when given a repo', function (st) { st.plan(1); - execa.stderr(bin, [ + execa(bin, [ '--no-config', '--no-ignore', '--use', - '../../index=repository:"wooorm/test"', - 'example.md', - 'examples/example.md' - ]).then(function (stderr) { + '../..=repository:"wooorm/test"', + 'github.md', + 'examples/github.md' + ]).then(function (result) { st.equal( - strip(stderr), + strip(result.stderr), [ - 'example.md', - ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, - ' 19:34-19:102 warning Link to unknown file: `examples/world.md` ' + source, - ' 21:12-21:81 warning Link to unknown file: `examples/world.md` ' + source, - ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, - ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, - ' 37:10-37:42 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 39:10-39:40 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 41:10-41:83 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 43:10-43:84 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 45:10-45:40 warning Link to unknown file: `examples/world.md` ' + source, - ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 47:10-47:38 warning Link to unknown file: `examples/world.md` ' + source, - ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 49:10-49:81 warning Link to unknown file: `examples/world.md` ' + source, - ' 49:10-49:81 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 51:10-51:82 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 51:10-51:82 warning Link to unknown file: `examples/world.md` ' + source, + 'examples/github.md', + ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, + ' 15:34-15:93 warning Link to unknown file: `world.md` ' + source, + ' 17:12-17:72 warning Link to unknown file: `world.md` ' + source, + ' 19:10-19:29 warning Link to unknown file: `world.md` ' + source, + ' 29:10-29:33 warning Link to unknown heading in `github.md`: `world` ' + source, + ' 31:10-31:73 warning Link to unknown heading in `github.md`: `world` ' + source, + ' 33:10-33:74 warning Link to unknown heading in `github.md`: `world` ' + source, + ' 35:10-35:32 warning Link to unknown file: `world.md` ' + source, + ' 35:10-35:32 warning Link to unknown heading in `world.md`: `hello` ' + source, + ' 37:10-37:72 warning Link to unknown file: `world.md` ' + source, + ' 37:10-37:72 warning Link to unknown heading in `world.md`: `hello` ' + source, + ' 39:10-39:73 warning Link to unknown heading in `world.md`: `hello` ' + source, + ' 39:10-39:73 warning Link to unknown file: `world.md` ' + source, '', - 'examples/example.md', - ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, - ' 15:34-15:93 warning Link to unknown file: `world.md` ' + source, - ' 17:12-17:72 warning Link to unknown file: `world.md` ' + source, - ' 19:10-19:29 warning Link to unknown file: `world.md` ' + source, - ' 29:10-29:34 warning Link to unknown heading in `example.md`: `world` ' + source, - ' 31:10-31:74 warning Link to unknown heading in `example.md`: `world` ' + source, - ' 33:10-33:75 warning Link to unknown heading in `example.md`: `world` ' + source, - ' 35:10-35:32 warning Link to unknown file: `world.md` ' + source, - ' 35:10-35:32 warning Link to unknown heading in `world.md`: `hello` ' + source, - ' 37:10-37:72 warning Link to unknown file: `world.md` ' + source, - ' 37:10-37:72 warning Link to unknown heading in `world.md`: `hello` ' + source, - ' 39:10-39:73 warning Link to unknown heading in `world.md`: `hello` ' + source, - ' 39:10-39:73 warning Link to unknown file: `world.md` ' + source, + 'github.md', + ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, + ' 19:34-19:102 warning Link to unknown file: `examples/world.md` ' + source, + ' 21:12-21:81 warning Link to unknown file: `examples/world.md` ' + source, + ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, + ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, + ' 37:10-37:41 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 39:10-39:39 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 41:10-41:82 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 43:10-43:83 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 45:10-45:40 warning Link to unknown file: `examples/world.md` ' + source, + ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 47:10-47:38 warning Link to unknown file: `examples/world.md` ' + source, + ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 49:10-49:81 warning Link to unknown file: `examples/world.md` ' + source, + ' 49:10-49:81 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 51:10-51:82 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 51:10-51:82 warning Link to unknown file: `examples/world.md` ' + source, '', '⚠ 30 warnings' ].join('\n'), 'should report' ); - }); + }, st.error); }); t.test('should work on GitHub URLs when with package.json', function (st) { @@ -218,80 +265,161 @@ test('remark-validate-links', function (t) { fs.unlinkSync('./package.json'); } - execa.stderr(bin, [ + execa(bin, [ '--no-config', '--no-ignore', '--use', '../..', - 'example.md', - 'examples/example.md' - ]).then(function (stderr) { + 'github.md', + 'examples/github.md' + ]).then(function (result) { clean(); st.equal( - strip(stderr), + strip(result.stderr), [ - 'example.md', - ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, - ' 19:34-19:102 warning Link to unknown file: `examples/world.md` ' + source, - ' 21:12-21:81 warning Link to unknown file: `examples/world.md` ' + source, - ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, - ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, - ' 37:10-37:42 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 39:10-39:40 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 41:10-41:83 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 43:10-43:84 warning Link to unknown heading in `examples/example.md`: `world`' + source, - ' 45:10-45:40 warning Link to unknown file: `examples/world.md` ' + source, - ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 47:10-47:38 warning Link to unknown file: `examples/world.md` ' + source, - ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 49:10-49:81 warning Link to unknown file: `examples/world.md` ' + source, - ' 49:10-49:81 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 51:10-51:82 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, - ' 51:10-51:82 warning Link to unknown file: `examples/world.md` ' + source, + 'examples/github.md', + ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, + ' 15:34-15:93 warning Link to unknown file: `world.md` ' + source, + ' 17:12-17:72 warning Link to unknown file: `world.md` ' + source, + ' 19:10-19:29 warning Link to unknown file: `world.md` ' + source, + ' 29:10-29:33 warning Link to unknown heading in `github.md`: `world` ' + source, + ' 31:10-31:73 warning Link to unknown heading in `github.md`: `world` ' + source, + ' 33:10-33:74 warning Link to unknown heading in `github.md`: `world` ' + source, + ' 35:10-35:32 warning Link to unknown file: `world.md` ' + source, + ' 35:10-35:32 warning Link to unknown heading in `world.md`: `hello` ' + source, + ' 37:10-37:72 warning Link to unknown file: `world.md` ' + source, + ' 37:10-37:72 warning Link to unknown heading in `world.md`: `hello` ' + source, + ' 39:10-39:73 warning Link to unknown heading in `world.md`: `hello` ' + source, + ' 39:10-39:73 warning Link to unknown file: `world.md` ' + source, '', - 'examples/example.md', - ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, - ' 15:34-15:93 warning Link to unknown file: `world.md` ' + source, - ' 17:12-17:72 warning Link to unknown file: `world.md` ' + source, - ' 19:10-19:29 warning Link to unknown file: `world.md` ' + source, - ' 29:10-29:34 warning Link to unknown heading in `example.md`: `world` ' + source, - ' 31:10-31:74 warning Link to unknown heading in `example.md`: `world` ' + source, - ' 33:10-33:75 warning Link to unknown heading in `example.md`: `world` ' + source, - ' 35:10-35:32 warning Link to unknown file: `world.md` ' + source, - ' 35:10-35:32 warning Link to unknown heading in `world.md`: `hello` ' + source, - ' 37:10-37:72 warning Link to unknown file: `world.md` ' + source, - ' 37:10-37:72 warning Link to unknown heading in `world.md`: `hello` ' + source, - ' 39:10-39:73 warning Link to unknown heading in `world.md`: `hello` ' + source, - ' 39:10-39:73 warning Link to unknown file: `world.md` ' + source, + 'github.md', + ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, + ' 19:34-19:102 warning Link to unknown file: `examples/world.md` ' + source, + ' 21:12-21:81 warning Link to unknown file: `examples/world.md` ' + source, + ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, + ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, + ' 37:10-37:41 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 39:10-39:39 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 41:10-41:82 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 43:10-43:83 warning Link to unknown heading in `examples/github.md`: `world`' + source, + ' 45:10-45:40 warning Link to unknown file: `examples/world.md` ' + source, + ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 47:10-47:38 warning Link to unknown file: `examples/world.md` ' + source, + ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 49:10-49:81 warning Link to unknown file: `examples/world.md` ' + source, + ' 49:10-49:81 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 51:10-51:82 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 51:10-51:82 warning Link to unknown file: `examples/world.md` ' + source, '', '⚠ 30 warnings' ].join('\n'), 'should report' ); - }, clean); + }, function (err) { + clean(); + st.error(err); + }); + }); + + t.test('should support a GitLab shortcode', function (st) { + st.plan(1); + + execa(bin, [ + '--no-config', + '--no-ignore', + '--use', + '../..=repository:"gitlab:wooorm/test"', + 'gitlab.md' + ]).then(function (result) { + st.equal( + strip(result.stderr), + [ + 'gitlab.md', + ' 5:37-5:51 warning Link to unknown heading: `world` ' + source, + ' 19:34-19:102 warning Link to unknown file: `examples/world.md`. Did you mean `examples/gitlab.md`' + source, + ' 21:12-21:81 warning Link to unknown file: `examples/world.md`. Did you mean `examples/gitlab.md`' + source, + ' 23:10-23:37 warning Link to unknown file: `examples/world.md`. Did you mean `examples/gitlab.md`' + source, + ' 25:10-25:35 warning Link to unknown file: `examples/world.md`. Did you mean `examples/gitlab.md`' + source, + ' 37:10-37:41 warning Link to unknown heading in `examples/gitlab.md`: `world` ' + source, + ' 39:10-39:39 warning Link to unknown heading in `examples/gitlab.md`: `world` ' + source, + ' 41:10-41:82 warning Link to unknown heading in `examples/gitlab.md`: `world` ' + source, + ' 43:10-43:83 warning Link to unknown heading in `examples/gitlab.md`: `world` ' + source, + ' 45:10-45:40 warning Link to unknown file: `examples/world.md`. Did you mean `examples/gitlab.md`' + source, + ' 45:10-45:40 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 47:10-47:38 warning Link to unknown file: `examples/world.md`. Did you mean `examples/gitlab.md`' + source, + ' 47:10-47:38 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 49:10-49:81 warning Link to unknown file: `examples/world.md`. Did you mean `examples/gitlab.md`' + source, + ' 49:10-49:81 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 51:10-51:82 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 51:10-51:82 warning Link to unknown file: `examples/world.md`. Did you mean `examples/gitlab.md`' + source, + '', + '⚠ 17 warnings' + ].join('\n'), + 'should report' + ); + }, st.error); + }); + + t.test('should support a Bitbucket shortcode', function (st) { + st.plan(1); + + execa(bin, [ + '--no-config', + '--no-ignore', + '--use', + '../..=repository:"bitbucket:wooorm/test"', + 'bitbucket.md' + ]).then(function (result) { + st.equal( + strip(result.stderr), + [ + 'bitbucket.md', + ' 5:37-5:67 warning Link to unknown heading: `world` ' + source, + ' 19:34-19:104 warning Link to unknown file: `examples/world.md` ' + source, + ' 21:12-21:83 warning Link to unknown file: `examples/world.md` ' + source, + ' 23:10-23:37 warning Link to unknown file: `examples/world.md` ' + source, + ' 25:10-25:35 warning Link to unknown file: `examples/world.md` ' + source, + ' 37:10-37:60 warning Link to unknown heading in `examples/bitbucket.md`: `world`' + source, + ' 39:10-39:58 warning Link to unknown heading in `examples/bitbucket.md`: `world`' + source, + ' 41:10-41:103 warning Link to unknown heading in `examples/bitbucket.md`: `world`' + source, + ' 43:10-43:104 warning Link to unknown heading in `examples/bitbucket.md`: `world`' + source, + ' 45:10-45:56 warning Link to unknown file: `examples/world.md` ' + source, + ' 45:10-45:56 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 47:10-47:54 warning Link to unknown file: `examples/world.md` ' + source, + ' 47:10-47:54 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 49:10-49:99 warning Link to unknown file: `examples/world.md` ' + source, + ' 49:10-49:99 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 51:10-51:100 warning Link to unknown heading in `examples/world.md`: `hello` ' + source, + ' 51:10-51:100 warning Link to unknown file: `examples/world.md` ' + source, + '', + '⚠ 17 warnings' + ].join('\n'), + 'should report' + ); + }, st.error); }); t.test('should suggest similar links', function (st) { st.plan(1); - execa.stderr(bin, [ + execa(bin, [ '--no-config', '--no-ignore', '--use', - '../../index', + '../..', 'suggestions.md' - ]).then(function (stderr) { + ]).then(function (result) { st.equal( - strip(stderr), + strip(result.stderr), [ 'suggestions.md', - ' 3:22-3:37 warning Link to unknown heading: `helloo`. Did you mean `hello` ' + source, - ' 7:17-7:40 warning Link to unknown heading in `example.md`: `fiiiles`. Did you mean `files`' + source, + ' 3:22-3:37 warning Link to unknown heading: `helloo`. Did you mean `hello` ' + source, + ' 7:17-7:39 warning Link to unknown heading in `github.md`: `fiiiles`. Did you mean `files`' + source, '', '⚠ 2 warnings' ].join('\n'), 'should report' ); - }); + }, st.error); }); });