-
-
Notifications
You must be signed in to change notification settings - Fork 26
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Use async file system operations * Check definitions, not references * Get repo info from Git instead of `package.json`, supporting non-npm projects * Support checking files on the API * Support non-hosted Git repos * Support self-hosted Git repos * Support case-insensitive hashes * Support streaming * Support multiple Git repos * Supports Gists * Add many more tests Closes GH-28. Closes GH-39. Closes GH-41.
- Loading branch information
Showing
24 changed files
with
1,286 additions
and
590 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use strict' | ||
|
||
module.exports = checkIfReferencedFilesExist | ||
|
||
function checkIfReferencedFilesExist(ctx, next) { | ||
next() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
'use strict' | ||
|
||
var fs = require('fs') | ||
|
||
module.exports = checkIfReferencedFilesExist | ||
|
||
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) | ||
} | ||
} | ||
|
||
expected = filePaths.length | ||
|
||
if (expected === 0) { | ||
next() | ||
} else { | ||
filePaths.forEach(checkIfExists) | ||
} | ||
|
||
function checkIfExists(filePath) { | ||
fs.access(filePath, fs.F_OK, onaccess) | ||
|
||
function onaccess(err) { | ||
var noEntry = err && err.code === 'ENOENT' | ||
|
||
landmarks[filePath] = !noEntry | ||
|
||
if (++actual === expected) { | ||
next() | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
'use strict' | ||
|
||
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() | ||
.use(mergeLandmarks) | ||
.use(mergeReferences) | ||
.use(checkIfReferencedFilesExist) | ||
.use(validate) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
'use strict' | ||
|
||
var constants = require('../constants') | ||
|
||
module.exports = mergeLandmarks | ||
|
||
function mergeLandmarks(ctx) { | ||
var result = {} | ||
var files = ctx.files | ||
var length = files.length | ||
var index = -1 | ||
var file | ||
var landmarks | ||
var landmark | ||
|
||
while (++index < length) { | ||
file = files[index] | ||
landmarks = file.data[constants.landmarkId] | ||
|
||
if (landmarks) { | ||
for (landmark in landmarks) { | ||
result[landmark] = true | ||
} | ||
} | ||
} | ||
|
||
ctx.landmarks = result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
'use strict' | ||
|
||
var constants = require('../constants') | ||
|
||
module.exports = mergeReferences | ||
|
||
function mergeReferences(ctx) { | ||
var result = {} | ||
var files = ctx.files | ||
var length = files.length | ||
var index = -1 | ||
var file | ||
var references | ||
var reference | ||
|
||
while (++index < length) { | ||
file = files[index] | ||
references = file.data[constants.referenceId] | ||
|
||
if (!references) { | ||
continue | ||
} | ||
|
||
for (reference in references) { | ||
if (!(reference in result)) { | ||
result[reference] = [] | ||
} | ||
|
||
result[reference].push({ | ||
file: file, | ||
reference: reference, | ||
nodes: references[reference] | ||
}) | ||
} | ||
} | ||
|
||
ctx.references = result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
'use strict' | ||
|
||
var path = require('path') | ||
var propose = require('propose') | ||
var constants = require('../constants') | ||
|
||
module.exports = validate | ||
|
||
function validate(ctx) { | ||
var landmarks = ctx.landmarks | ||
var references = ctx.references | ||
var missing = [] | ||
var reference | ||
var length | ||
var index | ||
|
||
for (reference in references) { | ||
if (!landmarks[reference]) { | ||
missing = missing.concat(references[reference]) | ||
} | ||
} | ||
|
||
length = missing.length | ||
index = -1 | ||
|
||
while (++index < length) { | ||
reference = missing[index] | ||
warn(ctx, reference.reference, reference.file, reference.nodes) | ||
} | ||
} | ||
|
||
function warn(ctx, reference, file, nodes) { | ||
var landmarks = ctx.landmarks | ||
var absolute = file.path ? path.resolve(file.cwd, file.path) : '' | ||
var base = absolute ? path.dirname(absolute) : null | ||
var relative = base ? path.relative(base, reference) : reference | ||
var pathname = relative | ||
var numberSignIndex = pathname.indexOf('#') | ||
var dictionary = [] | ||
var subhash | ||
var subpathname | ||
var landmark | ||
var relativeLandmark | ||
var hash | ||
var reason | ||
var ruleId | ||
var origin | ||
var suggestion | ||
|
||
if (numberSignIndex !== -1) { | ||
hash = pathname.slice(numberSignIndex + 1) | ||
pathname = pathname.slice(0, numberSignIndex) | ||
} | ||
|
||
if (hash) { | ||
reason = 'Link to unknown heading' | ||
ruleId = constants.headingRuleId | ||
|
||
if (base && path.join(base, pathname) !== absolute) { | ||
reason += ' in `' + pathname + '`' | ||
ruleId = constants.headingInFileRuleId | ||
} | ||
|
||
reason += ': `' + hash + '`' | ||
} else { | ||
reason = 'Link to unknown file: `' + pathname + '`' | ||
ruleId = constants.fileRuleId | ||
} | ||
|
||
origin = [constants.sourceId, ruleId].join(':') | ||
|
||
for (landmark in landmarks) { | ||
if (landmarks[landmark]) { | ||
relativeLandmark = base ? path.relative(base, landmark) : landmark | ||
subpathname = relativeLandmark | ||
subhash = null | ||
numberSignIndex = subpathname.indexOf('#') | ||
|
||
if (numberSignIndex !== -1) { | ||
subhash = subpathname.slice(numberSignIndex + 1) | ||
subpathname = subpathname.slice(0, numberSignIndex) | ||
} | ||
|
||
if (subpathname === pathname) { | ||
if (subhash && hash) { | ||
dictionary.push(subhash) | ||
} | ||
} else if (!subhash && !hash) { | ||
dictionary.push(subpathname) | ||
} | ||
} | ||
} | ||
|
||
suggestion = propose(hash ? hash : pathname, dictionary, {threshold: 0.7}) | ||
|
||
if (suggestion) { | ||
reason += '. Did you mean `' + suggestion + '`' | ||
} | ||
|
||
nodes.forEach(one) | ||
|
||
function one(node) { | ||
file.message(reason, node, origin) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,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' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
'use strict' | ||
|
||
var hostedGitInfo = require('hosted-git-info') | ||
|
||
module.exports = config | ||
|
||
var viewPaths = {github: 'blob', gitlab: 'blob', bitbucket: 'src'} | ||
var headingPrefixes = {github: '#', gitlab: '#', bitbucket: '#markdown-header-'} | ||
var lineLinks = {github: true, gitlab: true} | ||
|
||
function config(ctx) { | ||
var repo = ctx.repository | ||
var urlConfig = ctx.urlConfig | ||
var info = {} | ||
|
||
if (urlConfig) { | ||
return | ||
} | ||
|
||
urlConfig = {prefix: '', headingPrefix: '#', lines: false, hostname: null} | ||
|
||
if (repo) { | ||
info = hostedGitInfo.fromUrl(repo) | ||
} | ||
|
||
if (info) { | ||
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 lineLinks) { | ||
urlConfig.lines = lineLinks[info.type] | ||
} | ||
|
||
urlConfig.hostname = info.domain | ||
} | ||
|
||
ctx.urlConfig = urlConfig | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
'use strict' | ||
|
||
module.exports = findRepo | ||
|
||
function findRepo(ctx, next) { | ||
next() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
'use strict' | ||
|
||
var path = require('path') | ||
var exec = require('child_process').exec | ||
|
||
module.exports = findRepo | ||
|
||
function findRepo(ctx, next) { | ||
var repo = ctx.repository | ||
var file = ctx.file | ||
var base = file.cwd | ||
var actual = 0 | ||
var expected = 0 | ||
|
||
if (file.path) { | ||
base = path.dirname(path.resolve(base, file.path)) | ||
} | ||
|
||
if (repo === null || repo === undefined) { | ||
expected++ | ||
exec('git remote -v', {cwd: base}, onremote) | ||
} | ||
|
||
if (ctx.root === null || ctx.root === undefined) { | ||
if (repo === null || repo === undefined) { | ||
expected++ | ||
exec('git rev-parse --show-cdup', {cwd: base}, oncdup) | ||
} else { | ||
ctx.root = ctx.file.cwd | ||
} | ||
} else { | ||
ctx.root = path.resolve(file.cwd, ctx.root) | ||
} | ||
|
||
if (actual === expected) { | ||
next() | ||
} | ||
|
||
function onremote(err, stdout) { | ||
var remote | ||
|
||
if (err) { | ||
expected = Infinity | ||
return next(err) | ||
} | ||
|
||
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(err, stdout) { | ||
var out | ||
|
||
if (err) { | ||
expected = Infinity | ||
return next(err) | ||
} | ||
|
||
out = stdout.trim() | ||
ctx.root = out ? path.join(base, out) : base | ||
|
||
if (++actual === expected) { | ||
next() | ||
} | ||
} | ||
} |
Oops, something went wrong.