Skip to content

Commit

Permalink
feat!: add errors for relative internal links
Browse files Browse the repository at this point in the history
  • Loading branch information
HiDeoo committed Dec 12, 2023
1 parent ea4128d commit af18594
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 6 deletions.
24 changes: 24 additions & 0 deletions docs/src/content/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,27 @@ export default defineConfig({
],
})
```

### `errorOnRelativeLinks`

**Type:** `boolean`
**Default:** `true`

Relative internal links, such as `./test` or `../test`, are usually considered confusing as they can be difficult to reason about, figure out where they point to and require more maintenance when a page is moved.

By default, the Starlight Links Validator plugin will error if a relative internal link is found.
If you want to allow relative internal links, you can set this option to `false` but note that theses links will not be validated.

```js {6}
export default defineConfig({
integrations: [
starlight({
plugins: [
starlightLinksValidator({
errorOnRelativeLinks: false,
}),
],
}),
],
})
```
6 changes: 6 additions & 0 deletions packages/starlight-links-validator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ const starlightLinksValidatorOptionsSchema = z
* @see https://starlight.astro.build/guides/i18n/#fallback-content
*/
errorOnFallbackPages: z.boolean().default(true),
/**
* Defines whether the plugin should error on internal relative links.
*
* When set to `false`, the plugin will ignore relative links (e.g. `./foo` or `../bar`).
*/
errorOnRelativeLinks: z.boolean().default(true),
})
.default({})

Expand Down
2 changes: 1 addition & 1 deletion packages/starlight-links-validator/libs/remark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function getValidationData() {
}

function isInternalLink(link: string) {
return nodePath.isAbsolute(link) || link.startsWith('#')
return nodePath.isAbsolute(link) || link.startsWith('#') || link.startsWith('.')
}

function normalizeFilePath(filePath?: string) {
Expand Down
10 changes: 9 additions & 1 deletion packages/starlight-links-validator/libs/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ export function logErrors(errors: ValidationErrors) {
* Validate a link to another internal page that may or may not have a hash.
*/
function validateLink(context: ValidationContext) {
const { errors, filePath, link, outputDir, pages } = context
const { errors, filePath, link, options, outputDir, pages } = context

const sanitizedLink = link.replace(/^\//, '')
const segments = sanitizedLink.split('#')
Expand All @@ -96,6 +96,14 @@ function validateLink(context: ValidationContext) {
throw new Error('Failed to validate a link with no path.')
}

if (path.startsWith('.')) {
if (options.errorOnRelativeLinks) {
addError(errors, filePath, link)
}

return
}

if (isValidAsset(path, outputDir)) {
return
}
Expand Down
13 changes: 11 additions & 2 deletions packages/starlight-links-validator/tests/basics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ test('should build with valid links', async () => {
})

test('should not build with invalid links', async () => {
expect.assertions(4)
expect.assertions(5)

try {
await loadFixture('basics-invalid-links')
} catch (error) {
expect(error).toMatch(/Found 20 invalid links in 3 files./)
expect(error).toMatch(/Found 25 invalid links in 4 files./)

expect(error).toMatch(
new RegExp(`▶ test/
Expand Down Expand Up @@ -49,5 +49,14 @@ test('should not build with invalid links', async () => {
├─ #some-other-content
└─ /guides/namespacetest/#another-content`),
)

expect(error).toMatch(
new RegExp(`▶ relative/
├─ .
├─ ./relative
├─ ./test
├─ ./guides/example
└─ ../test`),
)
}
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Relative
---

- [Link to the same page](.)
- [Link to the same page with its name](./relative)
- [Link to another page in the same directory](./test)
- [Link to another page in another directory](./guides/example)
- [Link to another page in a parent directory](../test)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import starlight from '@astrojs/starlight'
import { defineConfig } from 'astro/config'

import starlightLinksValidator from '../..'

export default defineConfig({
integrations: [
starlight({
plugins: [starlightLinksValidator({ errorOnRelativeLinks: false })],
title: 'Starlight Links Validator Tests - relative ignore',
}),
],
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Example
---

## Description

This is an example page.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
title: Relative
---

- [Link to the same page](.)
- [Link to the same page with its name](./relative)
- [Link to another page in the same directory](./test)
- [Link to another page in another directory](./guides/example)
- [Link to another page in a parent directory](../test)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Test
---

# Some links

- [External link](https://starlight.astro.build/)

- [Example page](/guides/example)
- [Example page](/guides/example/)

- [Example page with hash](/guides/example#description)
- [Example page with hash](/guides/example/#description)

- [Unknown page](/unknown)
- [Unknown page](/unknown/)

- [Example page with unknown hash](/guides/example#unknown)
- [Example page with unknown hash](/guides/example/#unknown)
21 changes: 21 additions & 0 deletions packages/starlight-links-validator/tests/relative.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { expect, test } from 'vitest'

import { loadFixture } from './utils'

test('should ignore relative links when the `errorOnRelativeLinks` option is set to `false`', async () => {
expect.assertions(2)

try {
await loadFixture('relative-ignore')
} catch (error) {
expect(error).toMatch(/Found 4 invalid links in 1 file./)

expect(error).toMatch(
new RegExp(`▶ test/
├─ /unknown
├─ /unknown/
├─ /guides/example#unknown
└─ /guides/example/#unknown`),
)
}
})
4 changes: 2 additions & 2 deletions packages/starlight-links-validator/tests/trailing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { expect, test } from 'vitest'

import { loadFixture } from './utils'

test('should validate links when the `trailingSlash` option is set to `never`', async () => {
test('should validate links when the `trailingSlash` Astro option is set to `never`', async () => {
expect.assertions(2)

try {
Expand All @@ -20,7 +20,7 @@ test('should validate links when the `trailingSlash` option is set to `never`',
}
})

test('should validate links when the `trailingSlash` option is set to `always`', async () => {
test('should validate links when the `trailingSlash` Astro option is set to `always`', async () => {
expect.assertions(2)

try {
Expand Down

0 comments on commit af18594

Please sign in to comment.