Skip to content

Commit

Permalink
Implement "did you mean?" for link hygiene.
Browse files Browse the repository at this point in the history
Chasing the actual link without hints is not fun.
  • Loading branch information
olafurpg committed Sep 9, 2018
1 parent 8030e21 commit a0819c2
Show file tree
Hide file tree
Showing 5 changed files with 51 additions and 8 deletions.
1 change: 1 addition & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ lazy val mdoc = project
"com.vladsch.flexmark" % "flexmark-all" % "0.26.4",
"com.lihaoyi" %% "fansi" % "0.2.5",
"io.methvin" % "directory-watcher" % "0.7.0",
"me.xdrop" % "fuzzywuzzy" % "1.1.9", // for link hygiene "did you mean?"
"ch.epfl.scala" %% "scalafix-core" % V.scalafix
)
)
Expand Down
4 changes: 4 additions & 0 deletions docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -337,9 +337,13 @@ invalid.
# My title

Link to [my title](#my-title).
Link to [typo section](#mytitle).
Link to [old section](#doesnotexist).
```

Observe that mdoc suggests a fix if there exists a header that is similar to the
unknown link.

### Script semantics

Mdoc interprets code fences as normal Scala programs instead of as if they're
Expand Down
34 changes: 30 additions & 4 deletions mdoc/src/main/scala/mdoc/internal/markdown/LinkHygiene.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package mdoc.internal.markdown

import java.net.URI
import mdoc.Reporter
import me.xdrop.fuzzywuzzy.FuzzySearch

object LinkHygiene {
def lint(docs: List[DocumentLinks], reporter: Reporter, verbose: Boolean): Unit = {
Expand All @@ -16,16 +17,41 @@ object LinkHygiene {
} {
val isAbsolutePath = uri.getPath.startsWith("/")
val debug =
if (verbose) s". isValidHeading=$isValidHeading"
else ""
if (verbose) {
val query = uri.toString
val candidates = isValidHeading
.map { candidate =>
val score = FuzzySearch.ratio(candidate.toString, query)
score -> f"$score%-3s $candidate"
}
.toSeq
.sortBy(-_._1)
.map(_._2)
.mkString("\n ")
s"\nisValidHeading:\n $candidates"
} else ""
val help = getSimilarHeading(isValidHeading, uri) match {
case None => "."
case Some(similar) => s", did you mean '$similar'?"
}
val hint =
if (isAbsolutePath)
s". To fix this problem, either make the link relative or turn it into complete URL such as http://example.com$uri."
s" To fix this problem, either make the link relative or turn it into complete URL such as http://example.com$uri."
else ""
reporter.warning(reference.pos, s"Unknown link '$uri'$hint$debug")
reporter.warning(reference.pos, s"Unknown link '$uri'$help$hint$debug")
}
}

private def getSimilarHeading(candidates: Set[URI], query: URI): Option[URI] = {
val queryString = query.toString
val similar = for {
candidate <- candidates.iterator
score = FuzzySearch.ratio(queryString, candidate.toString)
if score > 90 // discard noisy candidates
} yield score -> candidate
if (similar.isEmpty) None
else Some(similar.maxBy(_._1)._2)
}
private def resolve(baseUri: URI, reference: String): Option[URI] = {
try {
Some(baseUri.resolve(reference).normalize())
Expand Down
9 changes: 8 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -503,18 +503,25 @@ Before:
# My title
Link to [my title](#my-title).
Link to [typo section](#mytitle).
Link to [old section](#doesnotexist).
````

Error:

````
warning: readme.md:4:9: warning: Unknown link 'readme.md#doesnotexist'
warning: readme.md:4:9: warning: Unknown link 'readme.md#mytitle', did you mean 'readme.md#my-title'?
Link to [typo section](#mytitle).
^^^^^^^^^^^^^^^^^^^^^^^^
warning: readme.md:5:9: warning: Unknown link 'readme.md#doesnotexist'.
Link to [old section](#doesnotexist).
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
````


Observe that mdoc suggests a fix if there exists a header that is similar to the
unknown link.

### Script semantics

Mdoc interprets code fences as normal Scala programs instead of as if they're
Expand Down
11 changes: 8 additions & 3 deletions tests/unit/src/test/scala/tests/markdown/LinkHygieneSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ class LinkHygieneSuite extends FunSuite with DiffAssertions {
|* [name](a.md#name)
|
""".stripMargin,
"""|warning: a.md:3:7: warning: Unknown link 'a.md#does-not-exist'
"""|warning: a.md:3:7: warning: Unknown link 'a.md#does-not-exist'.
|Error [link](#does-not-exist) failed.
| ^^^^^^^^^^^^^^^^^^^^^^^
|warning: a.md:4:6: warning: Unknown link 'a.md#sectionn'
|warning: a.md:4:6: warning: Unknown link 'a.md#sectionn', did you mean 'a.md#section'?
|Typo [section](#sectionn) failed.
| ^^^^^^^^^^^^^^^^^^^^
""".stripMargin
Expand Down Expand Up @@ -122,7 +122,12 @@ class LinkHygieneSuite extends FunSuite with DiffAssertions {
|/b.md
|# Header 2
""".stripMargin,
"""|warning: a.md:2:1: warning: Unknown link 'b.md#header'. isValidHeading=Set(b.md, b.md#header-2, a.md, a.md#header-1)
"""|warning: a.md:2:1: warning: Unknown link 'b.md#header', did you mean 'b.md#header-2'?
|isValidHeading:
| 92 b.md#header-2
| 83 a.md#header-1
| 53 b.md
| 40 a.md
|[2](b.md#header)
|^^^^^^^^^^^^^^^^
|""".stripMargin,
Expand Down

0 comments on commit a0819c2

Please sign in to comment.