-
Notifications
You must be signed in to change notification settings - Fork 466
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Verify links and anchors in the documentation (#1911)
- Loading branch information
Showing
12 changed files
with
245 additions
and
51 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
113 changes: 113 additions & 0 deletions
113
build-logic/base/src/main/groovy/org/spockframework/gradle/AsciiDocLinkVerifier.groovy
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,113 @@ | ||
package org.spockframework.gradle | ||
|
||
import groovy.transform.CompileDynamic | ||
import groovy.transform.CompileStatic | ||
import groovy.xml.XmlSlurper | ||
import org.ccil.cowan.tagsoup.Parser | ||
|
||
@CompileStatic | ||
class AsciiDocLinkVerifier { | ||
static verifyAnchorlessCrossDocumentLinks(Iterable<File> sourceFiles) { | ||
sourceFiles | ||
.collectMany { file -> | ||
if ((file.name == 'index.adoc') || (!file.name.endsWith('.adoc'))) { | ||
return [] | ||
} | ||
|
||
return (file.text =~ /<<([^>#]+\.adoc)#,[^>]+>>/) | ||
.collect { List it -> it[1] } | ||
.unique() | ||
.collect { | ||
"$file.name contains a cross-document link to $it without anchor, this will break in one-page variant" | ||
} | ||
} | ||
.tap { | ||
if (it) { | ||
throw new IllegalArgumentException(it.join('\n')) | ||
} | ||
} | ||
} | ||
|
||
@CompileDynamic | ||
static verifyLinksAndAnchors(Iterable<File> outputFiles) { | ||
outputFiles | ||
.collectMany { file -> | ||
if (!file.name.endsWith('.html')) { | ||
return [] | ||
} | ||
|
||
def xmlSlurper = new XmlSlurper(new Parser()) | ||
|
||
def subject = xmlSlurper.parse(file) | ||
|
||
// collect all relative link targets | ||
def relativeLinkTargets = subject | ||
.'**' | ||
.findAll { it.name() == 'a' } | ||
*.@href | ||
*.text() | ||
.collect { URI.create(it) } | ||
.findAll { | ||
!it.scheme && | ||
!it.authority && | ||
!it.userInfo && | ||
!it.host && | ||
it.port == -1 | ||
} | ||
|
||
// verify there are no dead links in the generated docs | ||
def result = relativeLinkTargets | ||
.findAll { it.path } | ||
*.path | ||
.findAll { !new File(file.parentFile, it).file } | ||
.collect { "$file.name contains a dead link to $it" } | ||
|
||
// verify there are no dead cross-document anchors in the generated docs | ||
result.addAll( | ||
relativeLinkTargets | ||
.findAll { it.path } | ||
.collect { linkTarget -> | ||
def linkTargetFile = new File(file.parentFile, linkTarget.path) | ||
if (!linkTargetFile.file || !linkTarget.fragment) { | ||
return | ||
} | ||
if ( | ||
!xmlSlurper | ||
.parse(linkTargetFile) | ||
.'**' | ||
.find { it.@id == linkTarget.fragment } | ||
) { | ||
return "$file.name contains a dead anchor to $linkTarget" | ||
} | ||
} | ||
.findAll() | ||
) | ||
|
||
// verify there are no dead in-document anchors in the generated docs | ||
result.addAll( | ||
relativeLinkTargets | ||
.findAll { !it.path } | ||
*.fragment | ||
.findAll { fragment -> !subject.'**'.find { it.@id == fragment } } | ||
.collect { "$file.name contains a dead anchor to $it" } | ||
.findAll() | ||
) | ||
|
||
// verify there are no duplicate anchors in the generated docs | ||
return result + subject | ||
.'**' | ||
*.@id | ||
*.text() | ||
.findAll() | ||
.groupBy() | ||
.findAll { key, value -> value.size() > 1 } | ||
*.key | ||
.collect { "$file.name contains multiple anchors with the name '$it'" } | ||
} | ||
.tap { | ||
if (it) { | ||
throw new IllegalArgumentException(it.join('\n')) | ||
} | ||
} | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
build-logic/base/src/test/groovy/org/spockframework/gradle/AsciiDocLinkVerifierTest.groovy
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,88 @@ | ||
package org.spockframework.gradle | ||
|
||
|
||
import spock.lang.Specification | ||
import spock.lang.TempDir | ||
import spock.util.io.FileSystemFixture | ||
|
||
import static java.nio.file.Files.list | ||
import static org.spockframework.gradle.AsciiDocLinkVerifier.verifyAnchorlessCrossDocumentLinks | ||
import static org.spockframework.gradle.AsciiDocLinkVerifier.verifyLinksAndAnchors | ||
|
||
class AsciiDocLinkVerifierTest extends Specification { | ||
@TempDir | ||
FileSystemFixture tempDir | ||
|
||
def "anchorless cross document links are detected"() { | ||
given: | ||
tempDir.create { | ||
file('foo.adoc') << ''' | ||
= Foo | ||
<<bar.adoc#,Bar>> | ||
'''.stripIndent(true) | ||
|
||
file('bar.adoc') << ''' | ||
= Bar | ||
<<foo.adoc#,Foo>> | ||
'''.stripIndent(true) | ||
} | ||
|
||
when: | ||
verifyAnchorlessCrossDocumentLinks(list(tempDir.currentPath).map { it.toFile() }.toList()) | ||
|
||
then: | ||
IllegalArgumentException e = thrown() | ||
e.message == ''' | ||
bar.adoc contains a cross-document link to foo.adoc without anchor, this will break in one-page variant | ||
foo.adoc contains a cross-document link to bar.adoc without anchor, this will break in one-page variant | ||
'''.stripIndent(true).trim() | ||
} | ||
|
||
def "anchorless cross document links are accepted in index.adoc"() { | ||
given: | ||
tempDir.create { | ||
file('foo.adoc') << ''' | ||
= Foo | ||
'''.stripIndent(true) | ||
|
||
file('index.adoc') << ''' | ||
= Index | ||
<<foo.adoc#,Foo>> | ||
'''.stripIndent(true) | ||
} | ||
|
||
when: | ||
verifyAnchorlessCrossDocumentLinks(list(tempDir.currentPath).map { it.toFile() }.toList()) | ||
|
||
then: | ||
noExceptionThrown() | ||
} | ||
|
||
def "problems with links and anchors are detected"() { | ||
given: | ||
tempDir.create { | ||
file('foo.html') << ''' | ||
<a href="bar.html"/> | ||
<a href="baz.html#baz"/> | ||
<a href="#foo"/> | ||
<div id="foo1"/> | ||
<div id="foo1"/> | ||
'''.stripIndent(true) | ||
|
||
file('baz.html') << ''' | ||
'''.stripIndent(true) | ||
} | ||
|
||
when: | ||
verifyLinksAndAnchors(list(tempDir.currentPath).map { it.toFile() }.toList()) | ||
|
||
then: | ||
IllegalArgumentException e = thrown() | ||
e.message == ''' | ||
foo.html contains a dead link to bar.html | ||
foo.html contains a dead anchor to baz.html#baz | ||
foo.html contains a dead anchor to foo | ||
foo.html contains multiple anchors with the name 'foo1' | ||
'''.stripIndent(true).trim() | ||
} | ||
} |
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
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
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
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
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
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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
[[spring-module]] | ||
= Spring Module | ||
include::include.adoc[] | ||
|
||
|
Oops, something went wrong.