Skip to content

Commit

Permalink
Rewrite module
Browse files Browse the repository at this point in the history
* 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
wooorm committed Jun 23, 2019
1 parent f04d6b3 commit 87186af
Show file tree
Hide file tree
Showing 24 changed files with 1,286 additions and 590 deletions.
427 changes: 1 addition & 426 deletions index.js

Large diffs are not rendered by default.

7 changes: 7 additions & 0 deletions lib/check/check-files.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

module.exports = checkIfReferencedFilesExist

function checkIfReferencedFilesExist(ctx, next) {
next()
}
42 changes: 42 additions & 0 deletions lib/check/check-files.js
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()
}
}
}
}
13 changes: 13 additions & 0 deletions lib/check/index.js
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)
28 changes: 28 additions & 0 deletions lib/check/merge-landmarks.js
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
}
38 changes: 38 additions & 0 deletions lib/check/merge-references.js
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
}
105 changes: 105 additions & 0 deletions lib/check/validate.js
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)
}
}
8 changes: 8 additions & 0 deletions lib/constants.js
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'
43 changes: 43 additions & 0 deletions lib/find/config.js
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
}
7 changes: 7 additions & 0 deletions lib/find/find-repo.browser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
'use strict'

module.exports = findRepo

function findRepo(ctx, next) {
next()
}
76 changes: 76 additions & 0 deletions lib/find/find-repo.js
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()
}
}
}
Loading

0 comments on commit 87186af

Please sign in to comment.