From 32a92f867a32241bf38172a87fa1c07a8b3d7708 Mon Sep 17 00:00:00 2001
From: HiDeoo <494699+HiDeoo@users.noreply.github.com>
Date: Thu, 12 Sep 2024 17:45:19 +0200
Subject: [PATCH] feat: adds a new `errorOnInvalidHashes` option defaulting to
`true` to disable hash validation
---
docs/src/content/docs/configuration.md | 24 ++++++++++
docs/src/content/docs/getting-started.mdx | 4 +-
packages/starlight-links-validator/README.md | 4 +-
packages/starlight-links-validator/index.ts | 8 ++++
.../libs/validation.ts | 16 ++++---
.../tests/base-path.test.ts | 4 +-
.../tests/basics.test.ts | 20 ++++----
.../tests/custom-ids.test.ts | 2 +-
.../tests/fallback.test.ts | 16 +++----
.../src/content/docs/guides/example.mdx | 12 ++---
.../src/content/docs/test.md | 12 ++---
.../src/content/docs/guides/example.mdx | 10 ++--
.../src/content/docs/index.md | 14 +++---
.../astro.config.ts | 13 +++++
.../src/content/docs/guides/example.mdx | 46 ++++++++++++++++++
.../src/content/docs/test.md | 48 +++++++++++++++++++
.../astro.config.ts | 13 +++++
.../src/content/docs/guides/example.mdx | 47 ++++++++++++++++++
.../src/content/docs/index.md | 38 +++++++++++++++
.../src/content/docs/test.md | 27 +++++++++++
.../tests/invalid-hashes.test.ts | 42 ++++++++++++++++
.../tests/relative.test.ts | 4 +-
.../tests/src-dir.test.ts | 4 +-
.../tests/trailing.test.ts | 8 ++--
24 files changed, 373 insertions(+), 63 deletions(-)
create mode 100644 packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/astro.config.ts
create mode 100644 packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/src/content/docs/guides/example.mdx
create mode 100644 packages/starlight-links-validator/tests/fixtures/invalid-hashes-invalid-links/src/content/docs/test.md
create mode 100644 packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/astro.config.ts
create mode 100644 packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/guides/example.mdx
create mode 100644 packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/index.md
create mode 100644 packages/starlight-links-validator/tests/fixtures/invalid-hashes-valid-links/src/content/docs/test.md
create mode 100644 packages/starlight-links-validator/tests/invalid-hashes.test.ts
diff --git a/docs/src/content/docs/configuration.md b/docs/src/content/docs/configuration.md
index 9b10fae..f97b527 100644
--- a/docs/src/content/docs/configuration.md
+++ b/docs/src/content/docs/configuration.md
@@ -101,6 +101,30 @@ export default defineConfig({
})
```
+### `errorOnInvalidHashes`
+
+**Type:** `boolean`
+**Default:** `true`
+
+By default, the Starlight Links Validator plugin will error if an internal link points to an [hash](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) that does not exist in the target page.
+If you want to only validate that pages exist but ignore hashes, you can set this option to `false`.
+
+This option should be used with caution but can be useful in large documentation with many contributors where hashes always being up-to-date can be difficult to maintain and validated on a different schedule, e.g. once a week.
+
+```js {6}
+export default defineConfig({
+ integrations: [
+ starlight({
+ plugins: [
+ starlightLinksValidator({
+ errorOnInvalidHashes: false,
+ }),
+ ],
+ }),
+ ],
+})
+```
+
### `exclude`
**Type:** `string[]`
diff --git a/docs/src/content/docs/getting-started.mdx b/docs/src/content/docs/getting-started.mdx
index 88071d4..37b90c6 100644
--- a/docs/src/content/docs/getting-started.mdx
+++ b/docs/src/content/docs/getting-started.mdx
@@ -5,8 +5,8 @@ title: Getting Started
A [Starlight](https://starlight.astro.build) plugin to validate **_internal_** links in Markdown and MDX files.
- Validate internal links to other pages
-- Validate internal links to anchors in other pages
-- Validate internal links to anchors in the same page
+- Validate internal links to hashes in other pages
+- Validate internal links to hashes in the same page
- Ignore external links
- Run only during a production build
diff --git a/packages/starlight-links-validator/README.md b/packages/starlight-links-validator/README.md
index f61dd63..db11b44 100644
--- a/packages/starlight-links-validator/README.md
+++ b/packages/starlight-links-validator/README.md
@@ -27,8 +27,8 @@ Want to get started immediately? Check out the [getting started guide](https://s
A [Starlight](https://starlight.astro.build) plugin to validate **_internal_** links in Markdown and MDX files.
- Validate internal links to other pages
-- Validate internal links to anchors in other pages
-- Validate internal links to anchors in the same page
+- Validate internal links to hashes in other pages
+- Validate internal links to hashes in the same page
- Ignore external links
- Run only during a production build
diff --git a/packages/starlight-links-validator/index.ts b/packages/starlight-links-validator/index.ts
index 2c9480f..0298fa1 100644
--- a/packages/starlight-links-validator/index.ts
+++ b/packages/starlight-links-validator/index.ts
@@ -33,6 +33,14 @@ const starlightLinksValidatorOptionsSchema = z
* @default true
*/
errorOnRelativeLinks: z.boolean().default(true),
+ /**
+ * Defines whether the plugin should error on invalid hashes.
+ *
+ * When set to `false`, the plugin will only validate link pages and ignore hashes.
+ *
+ * @default true
+ */
+ errorOnInvalidHashes: z.boolean().default(true),
/**
* Defines a list of links or glob patterns that should be excluded from validation.
*
diff --git a/packages/starlight-links-validator/libs/validation.ts b/packages/starlight-links-validator/libs/validation.ts
index a1754ab..cbc08bf 100644
--- a/packages/starlight-links-validator/libs/validation.ts
+++ b/packages/starlight-links-validator/libs/validation.ts
@@ -15,7 +15,7 @@ import { getValidationData, type Headings } from './remark'
export const ValidationErrorType = {
InconsistentLocale: 'inconsistent locale',
- InvalidAnchor: 'invalid anchor',
+ InvalidHash: 'invalid hash',
InvalidLink: 'invalid link',
RelativeLink: 'relative link',
TrailingSlash: 'trailing slash',
@@ -59,7 +59,9 @@ export function validateLinks(
}
if (link.startsWith('#')) {
- validateSelfAnchor(validationContext)
+ if (options.errorOnInvalidHashes) {
+ validateSelfHash(validationContext)
+ }
} else {
validateLink(validationContext)
}
@@ -151,7 +153,9 @@ function validateLink(context: ValidationContext) {
}
if (hash && !fileHeadings.includes(hash)) {
- addError(errors, filePath, link, ValidationErrorType.InvalidAnchor)
+ if (options.errorOnInvalidHashes) {
+ addError(errors, filePath, link, ValidationErrorType.InvalidHash)
+ }
return
}
@@ -176,9 +180,9 @@ function getFileHeadings(path: string, { headings, localeConfig, options }: Vali
}
/**
- * Validate a link to an anchor in the same page.
+ * Validate a link to an hash in the same page.
*/
-function validateSelfAnchor({ errors, link, filePath, headings }: ValidationContext) {
+function validateSelfHash({ errors, link, filePath, headings }: ValidationContext) {
const sanitizedHash = link.replace(/^#/, '')
const fileHeadings = headings.get(filePath)
@@ -187,7 +191,7 @@ function validateSelfAnchor({ errors, link, filePath, headings }: ValidationCont
}
if (!fileHeadings.includes(sanitizedHash)) {
- addError(errors, filePath, link, 'invalid anchor')
+ addError(errors, filePath, link, ValidationErrorType.InvalidHash)
}
}
diff --git a/packages/starlight-links-validator/tests/base-path.test.ts b/packages/starlight-links-validator/tests/base-path.test.ts
index d37059d..b766117 100644
--- a/packages/starlight-links-validator/tests/base-path.test.ts
+++ b/packages/starlight-links-validator/tests/base-path.test.ts
@@ -19,8 +19,8 @@ test('should validate links when the `base` Astro option is set', async () => {
['/guides/example/#description', ValidationErrorType.InvalidLink],
['/unknown', ValidationErrorType.InvalidLink],
['/unknown/', ValidationErrorType.InvalidLink],
- ['/test/guides/example#unknown', ValidationErrorType.InvalidAnchor],
- ['/test/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+ ['/test/guides/example#unknown', ValidationErrorType.InvalidHash],
+ ['/test/guides/example/#unknown', ValidationErrorType.InvalidHash],
['/favicon.svg', ValidationErrorType.InvalidLink],
['/guidelines/dummy.pdf', ValidationErrorType.InvalidLink],
])
diff --git a/packages/starlight-links-validator/tests/basics.test.ts b/packages/starlight-links-validator/tests/basics.test.ts
index 177c2ff..6c8cbba 100644
--- a/packages/starlight-links-validator/tests/basics.test.ts
+++ b/packages/starlight-links-validator/tests/basics.test.ts
@@ -27,35 +27,35 @@ test('should not build with invalid links', async () => {
['/unknown/', ValidationErrorType.InvalidLink],
['/unknown#title', ValidationErrorType.InvalidLink],
['/unknown/#title', ValidationErrorType.InvalidLink],
- ['#links', ValidationErrorType.InvalidAnchor],
- ['/guides/example/#links', ValidationErrorType.InvalidAnchor],
+ ['#links', ValidationErrorType.InvalidHash],
+ ['/guides/example/#links', ValidationErrorType.InvalidHash],
['/icon.svg', ValidationErrorType.InvalidLink],
['/guidelines/ui.pdf', ValidationErrorType.InvalidLink],
['/unknown-ref', ValidationErrorType.InvalidLink],
- ['#unknown-ref', ValidationErrorType.InvalidAnchor],
- ['#anotherDiv', ValidationErrorType.InvalidAnchor],
+ ['#unknown-ref', ValidationErrorType.InvalidHash],
+ ['#anotherDiv', ValidationErrorType.InvalidHash],
['/guides/page-with-custom-slug', ValidationErrorType.InvalidLink],
['/release/@pkg/v0.2.0', ValidationErrorType.InvalidLink],
])
expectValidationErrors(error, 'guides/example/', [
- ['#links', ValidationErrorType.InvalidAnchor],
+ ['#links', ValidationErrorType.InvalidHash],
['/unknown/#links', ValidationErrorType.InvalidLink],
['/unknown', ValidationErrorType.InvalidLink],
- ['#anotherBlock', ValidationErrorType.InvalidAnchor],
+ ['#anotherBlock', ValidationErrorType.InvalidHash],
['/icon.svg', ValidationErrorType.InvalidLink],
['/guidelines/ui.pdf', ValidationErrorType.InvalidLink],
['/linkcard/', ValidationErrorType.InvalidLink],
['/linkcard/#links', ValidationErrorType.InvalidLink],
- ['#linkcard', ValidationErrorType.InvalidAnchor],
+ ['#linkcard', ValidationErrorType.InvalidHash],
['/linkbutton/', ValidationErrorType.InvalidLink],
['/linkbutton/#links', ValidationErrorType.InvalidLink],
- ['#linkbutton', ValidationErrorType.InvalidAnchor],
+ ['#linkbutton', ValidationErrorType.InvalidHash],
])
expectValidationErrors(error, 'guides/namespacetest/', [
- ['#some-other-content', ValidationErrorType.InvalidAnchor],
- ['/guides/namespacetest/#another-content', ValidationErrorType.InvalidAnchor],
+ ['#some-other-content', ValidationErrorType.InvalidHash],
+ ['/guides/namespacetest/#another-content', ValidationErrorType.InvalidHash],
])
expectValidationErrors(error, 'relative/', [
diff --git a/packages/starlight-links-validator/tests/custom-ids.test.ts b/packages/starlight-links-validator/tests/custom-ids.test.ts
index 9fb6f74..7578879 100644
--- a/packages/starlight-links-validator/tests/custom-ids.test.ts
+++ b/packages/starlight-links-validator/tests/custom-ids.test.ts
@@ -12,6 +12,6 @@ test('should validate links with custom IDs', async () => {
} catch (error) {
expectValidationErrorCount(error, 1, 1)
- expectValidationErrors(error, 'test/', [['#heading-with-custom-id', ValidationErrorType.InvalidAnchor]])
+ expectValidationErrors(error, 'test/', [['#heading-with-custom-id', ValidationErrorType.InvalidHash]])
}
})
diff --git a/packages/starlight-links-validator/tests/fallback.test.ts b/packages/starlight-links-validator/tests/fallback.test.ts
index 0f5b90d..686ffdb 100644
--- a/packages/starlight-links-validator/tests/fallback.test.ts
+++ b/packages/starlight-links-validator/tests/fallback.test.ts
@@ -36,8 +36,8 @@ test('should not build with invalid fallback links', async () => {
expectValidationErrors(error, 'en/', [
['/en/guides/unknown', ValidationErrorType.InvalidLink],
['/en/guides/unknown/', ValidationErrorType.InvalidLink],
- ['/en/guides/example#unknown', ValidationErrorType.InvalidAnchor],
- ['/en/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+ ['/en/guides/example#unknown', ValidationErrorType.InvalidHash],
+ ['/en/guides/example/#unknown', ValidationErrorType.InvalidHash],
['/es/guides/example', ValidationErrorType.InvalidLink],
['/es/guides/example/', ValidationErrorType.InvalidLink],
])
@@ -45,8 +45,8 @@ test('should not build with invalid fallback links', async () => {
expectValidationErrors(error, 'fr/', [
['/fr/guides/unknown', ValidationErrorType.InvalidLink],
['/fr/guides/unknown/', ValidationErrorType.InvalidLink],
- ['/fr/guides/example#unknown', ValidationErrorType.InvalidAnchor],
- ['/fr/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+ ['/fr/guides/example#unknown', ValidationErrorType.InvalidHash],
+ ['/fr/guides/example/#unknown', ValidationErrorType.InvalidHash],
])
expectValidationErrors(error, 'fr/guides/test/', [['/', ValidationErrorType.InvalidLink]])
@@ -68,8 +68,8 @@ test('should not build with a root locale and invalid fallback links', async ()
expectValidationErrors(error, '/', [
['/guides/unknown', ValidationErrorType.InvalidLink],
['/guides/unknown/', ValidationErrorType.InvalidLink],
- ['/guides/example#unknown', ValidationErrorType.InvalidAnchor],
- ['/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+ ['/guides/example#unknown', ValidationErrorType.InvalidHash],
+ ['/guides/example/#unknown', ValidationErrorType.InvalidHash],
['/es/guides/example', ValidationErrorType.InvalidLink],
['/es/guides/example/', ValidationErrorType.InvalidLink],
])
@@ -77,8 +77,8 @@ test('should not build with a root locale and invalid fallback links', async ()
expectValidationErrors(error, 'fr/', [
['/fr/guides/unknown', ValidationErrorType.InvalidLink],
['/fr/guides/unknown/', ValidationErrorType.InvalidLink],
- ['/fr/guides/example#unknown', ValidationErrorType.InvalidAnchor],
- ['/fr/guides/example/#unknown', ValidationErrorType.InvalidAnchor],
+ ['/fr/guides/example#unknown', ValidationErrorType.InvalidHash],
+ ['/fr/guides/example/#unknown', ValidationErrorType.InvalidHash],
])
expectValidationErrors(error, 'guides/test/', [['/en', ValidationErrorType.InvalidLink]])
diff --git a/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/guides/example.mdx b/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/guides/example.mdx
index 5cff26e..972f2b1 100644
--- a/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/guides/example.mdx
+++ b/packages/starlight-links-validator/tests/fixtures/basics-invalid-links/src/content/docs/guides/example.mdx
@@ -13,8 +13,8 @@ import { Card, CardGrid, LinkCard, LinkButton } from '@astrojs/starlight/compone
## Some links
-- [Link to invalid anchor in the same page](#links)
-- [Link to invalid anchor in another page](/unknown/#links)
+- [Link to invalid hash in the same page](#links)
+- [Link to invalid hash in another page](/unknown/#links)
HTML link to unknown page
@@ -37,10 +37,10 @@ some content