diff --git a/README.md b/README.md index 9fb616d..84605d8 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ For e.g. With the following configuration and `../javadocs/1.0.x/overview-summary.html` as the link being checked, this rule checks for the existence of the `overview-summary.html` file at `../../static/javadocs/1.0.x/overview-summary.html`. -##### Note ##### +#### route-map Ordering Ensure each `route-map` pair is specific because the route-map option will validate relative links using the first matching regex found in the configuration. @@ -115,4 +115,4 @@ For e.g. With the following configuration and `../../javadocs/1.0.x/overview-summary.html` as the link being checked, this rule checks for the existence of the `overview-summary.html` file at the first destination `../website/static/javadocs/1.0.x/overview-summary.html` instead -of the second destination `../../static/javadocs/1.0.x/overview-summary.html`. +of the second destination `../../static/javadocs/1.0.x/overview-summary.html`. \ No newline at end of file diff --git a/src/no-dead-relative-link.js b/src/no-dead-relative-link.js index 6e44742..af2b4b3 100644 --- a/src/no-dead-relative-link.js +++ b/src/no-dead-relative-link.js @@ -6,7 +6,7 @@ import {parse, Syntax} from '@textlint/markdown-to-ast'; import {traverse, VisitorOption} from '@textlint/ast-traverse'; import GithubSlugger from 'github-slugger'; import util from 'util'; -import { wrapReportHandler} from 'textlint-rule-helper'; +import {wrapReportHandler} from 'textlint-rule-helper'; const fileRead = util.promisify(fs.readFile); @@ -39,11 +39,16 @@ async function validateLinkNode(linkNode, context, options) { async function validateRelativeLink(linkNode, context, options) { let linkURL = getLinkURL(linkNode.url, context, options); + let routedLinkURL; if (!await fileExists(url.fileURLToPath(linkURL))) { if (options["route-map"]) { - linkURL = await getRoutedLink(linkNode, context, options); - if (linkURL && !await fileExists(url.fileURLToPath(linkURL))) { - reportError(linkNode, context, `${path.basename(linkURL.pathname)} does not exist`); + routedLinkURL = await getRoutedLink(linkNode, context, options); + if (!routedLinkURL) { + reportError(linkNode, context, `${path.basename(linkURL.pathname)} has no mapped routing`); + return; + } + else if (!await fileExists(url.fileURLToPath(routedLinkURL))) { + reportError(linkNode, context, `The routed destination for ${path.basename(routedLinkURL.pathname)} does not exist`); return; } } else { @@ -52,6 +57,10 @@ async function validateRelativeLink(linkNode, context, options) { } } + // use the routedLinkURL to check for the anchor + if (routedLinkURL) { + linkURL = routedLinkURL; + } if (linkURL && linkURL.hash && path.extname(linkURL.pathname) === ".md") { return validateAnchorLink(url.fileURLToPath(linkURL), linkURL.hash.slice(1), linkNode, context); } @@ -60,14 +69,12 @@ async function validateRelativeLink(linkNode, context, options) { async function getRoutedLink(linkNode, context, options) { let linkRouteMaps = options["route-map"]; let nodeUrl = linkNode.url; - for (const mapping of linkRouteMaps) { let sourceRegex = new RegExp(mapping["source"], "g"); let mappedDestination = mapping["destination"] if (sourceRegex.test(nodeUrl)) { let routedUrl = nodeUrl.replace(sourceRegex, mappedDestination); - let linkURL = getLinkURL(routedUrl, context, options); - return linkURL; + return getLinkURL(routedUrl, context, options); } } } diff --git a/test/fixtures/testFiles/invalidLinkRoutingTest.md b/test/fixtures/testFiles/invalidLinkRoutingTest.md index a0604da..23065b6 100644 --- a/test/fixtures/testFiles/invalidLinkRoutingTest.md +++ b/test/fixtures/testFiles/invalidLinkRoutingTest.md @@ -1,5 +1,9 @@ -This invalid link to a file with an anchor has no valid routing [invalidAnchorLink](../../invalidLink.md#does-not-exist) -This invalid link to a file has no valid routing [invalidLink](../../invalidLink.md) -This invalid link to a html file with an anchor that needs to be resolved as markdown has no valid routing [invalidAnchorLink](../dir/invalidLink.html#does-not-exist) -This invalid link to html file that needs to be resolved as markdown has no valid routing [invalidLink](../dir/invalidLink.html) -This invalid link to a file with an anchor is routed to a valid link with an invalid anchor [invalidAnchorLink](../../subdir/linkTestFile.md#header-7) \ No newline at end of file +This invalid link to a file with an anchor is routed to a destination where the file doesn't exist [invalidAnchorLink](../dir/invalidLink.md#does-not-exist) +This invalid link to a file is routed to a destination where the file doesn't exist [invalidLink](../dir/invalidLink.md) +This invalid link to a html file with an anchor that needs to be resolved as markdown is routed to a destination where the file doesn't exist [invalidAnchorLink](../dir/invalidLink.html#does-not-exist) +This invalid link to html file that needs to be resolved as markdown is routed to a destination where the file doesn't exist [invalidLink](../dir/invalidLink.html) +This invalid link to a file with an anchor is routed to a valid destination, but the anchor is non-existent [invalidAnchorLink](../../subdir/linkTestFile.md#header-7) +This invalid link to a file has no matched routing [invalidAnchorLink](../../dir/subdir/linkTestFile.md) +This invalid link to a file with an anchor has no matched routing [invalidAnchorLink](../../dir/subdir/linkTestFile.md#header-7) +This invalid link to a file is routed to an invalid destination [invalidAnchorLink](../dir/subdir/linkTestFile.md) +This invalid link to a file with an anchor is routed to an invalid destination [invalidAnchorLink](../dir/subdir/linkTestFile.md#header-7) \ No newline at end of file diff --git a/test/no-dead-relative-link-test.js b/test/no-dead-relative-link-test.js index 4c02d9e..4d423cd 100644 --- a/test/no-dead-relative-link-test.js +++ b/test/no-dead-relative-link-test.js @@ -81,16 +81,24 @@ tester.run( "resolve-as-markdown": ".html", "route-map": [ { - "source": "../../invalidLink.md", - "destination": "../invalidLink.md" + "source": "^../dir/subdir", + "destination": "/dir/subdir" }, { - "source": "../dir/", + "source": "^../dir/", "destination": "../" }, { - "source": "../../(subdir)/", + "source": "^../../(subdir)/", "destination": "$1/" + }, + { + "source": "^../../linkTestFile.md", + "destination": "../linkTestFile.md" + }, + { + "source": "^../../linkTestFile.html", + "destination": "../linkTestFile.md" } ] } @@ -108,31 +116,144 @@ tester.run( inputPath: path.resolve("./test/fixtures/testFiles/invalidLinkRoutingTest.md"), errors: [ { - message: "invalidLink.md does not exist", + message: "The routed destination for invalidLink.md does not exist", line: 1, - column: 85 + column: 120 }, { - message: "invalidLink.md does not exist", + message: "The routed destination for invalidLink.md does not exist", line: 2, - column: 64 + column: 99 }, { - message: "invalidLink.md does not exist", + message: "The routed destination for invalidLink.md does not exist", line: 3, - column: 128 + column: 163 }, { - message: "invalidLink.md does not exist", + message: "The routed destination for invalidLink.md does not exist", line: 4, - column: 105 + column: 140 }, { message: "Anchor #header-7 does not exist in linkTestFile.md", line: 5, - column: 113 + column: 129 + }, + { + message: "linkTestFile.md has no mapped routing", + line: 6, + column: 72 + }, + { + message: "linkTestFile.md has no mapped routing", + line: 7, + column: 87 + }, + { + message: "The routed destination for linkTestFile.md does not exist", + line: 8, + column: 85 + }, + { + message: "The routed destination for linkTestFile.md does not exist", + line: 9, + column: 100 } ] } ] -}); \ No newline at end of file +}); +tester.run( + "no-dead-relative-links: with route-map only", + { + rules: [ + { + ruleId: "no-dead-relative-link", + rule: validateRelativeLinks, + options: { + "route-map": [ + { + "source": "^../dir/subdir", + "destination": "/dir/subdir" + }, + { + "source": "^../dir/", + "destination": "../" + }, + { + "source": "^../../(subdir)/", + "destination": "$1/" + }, + { + "source": "^../../linkTestFile.md", + "destination": "../linkTestFile.md" + }, + { + "source": "^../../linkTestFile.html", + "destination": "../linkTestFile.md" + } + ] + } + } + ] + }, + { + valid: [ + { + inputPath: path.resolve("./test/fixtures/testFiles/validLinkRoutingTest.md"), + } + ], + invalid: [ + { + inputPath: path.resolve("./test/fixtures/testFiles/invalidLinkRoutingTest.md"), + errors: [ + { + message: "The routed destination for invalidLink.md does not exist", + line: 1, + column: 120 + }, + { + message: "The routed destination for invalidLink.md does not exist", + line: 2, + column: 99 + }, + { + message: "The routed destination for invalidLink.html does not exist", + line: 3, + column: 163 + }, + { + message: "The routed destination for invalidLink.html does not exist", + line: 4, + column: 140 + }, + { + message: "Anchor #header-7 does not exist in linkTestFile.md", + line: 5, + column: 129 + }, + { + message: "linkTestFile.md has no mapped routing", + line: 6, + column: 72 + }, + { + message: "linkTestFile.md has no mapped routing", + line: 7, + column: 87 + }, + { + message: "The routed destination for linkTestFile.md does not exist", + line: 8, + column: 85 + }, + { + message: "The routed destination for linkTestFile.md does not exist", + line: 9, + column: 100 + } + ] + } + ] + }); \ No newline at end of file