mirror of
https://github.com/theoludwig/markdownlint-rule-relative-links.git
synced 2026-06-09 23:25:33 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
1385c4bd80
|
|||
| 6e899aa899 | |||
| 36ed1ee788 | |||
|
f48932001a
|
@@ -10,10 +10,10 @@ jobs:
|
|||||||
lint:
|
lint:
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
|
- uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6.0.3
|
||||||
|
|
||||||
- name: "Setup Node.js"
|
- name: "Setup Node.js"
|
||||||
uses: "actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238" # v6.2.0
|
uses: "actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e" # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ jobs:
|
|||||||
pull-requests: "write"
|
pull-requests: "write"
|
||||||
id-token: "write"
|
id-token: "write"
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
|
- uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6.0.3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: "Setup Node.js"
|
- name: "Setup Node.js"
|
||||||
uses: "actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238" # v6.2.0
|
uses: "actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e" # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ jobs:
|
|||||||
- "macos-latest"
|
- "macos-latest"
|
||||||
runs-on: "${{ matrix.runs-on }}"
|
runs-on: "${{ matrix.runs-on }}"
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
|
- uses: "actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10" # v6.0.3
|
||||||
|
|
||||||
- name: "Setup Node.js"
|
- name: "Setup Node.js"
|
||||||
uses: "actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238" # v6.2.0
|
uses: "actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e" # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: "lts/*"
|
node-version: "lts/*"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
|
|||||||
+7
-1
@@ -1,4 +1,10 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
"extends": ["node_modules/eslint-config-conventions/.oxlintrc.json"]
|
"extends": ["node_modules/eslint-config-conventions/.oxlintrc.json"],
|
||||||
|
"env": {
|
||||||
|
"builtin": true,
|
||||||
|
"browser": true,
|
||||||
|
"node": true,
|
||||||
|
"shared-node-browser": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ awesome.md:3 relative-links Relative links should be valid ["./invalid.txt" shou
|
|||||||
- Support links fragments similar to the [built-in `markdownlint` rule - MD051](https://github.com/DavidAnson/markdownlint/blob/main/doc/md051.md) (e.g: `[Link](./awesome.md#heading)`).
|
- Support links fragments similar to the [built-in `markdownlint` rule - MD051](https://github.com/DavidAnson/markdownlint/blob/main/doc/md051.md) (e.g: `[Link](./awesome.md#heading)`).
|
||||||
- Ignore external links and absolute paths as it only checks relative links (e.g: `https://example.com/` or `/absolute/path.png`).
|
- Ignore external links and absolute paths as it only checks relative links (e.g: `https://example.com/` or `/absolute/path.png`).
|
||||||
- If necessary, absolute paths can be validated too, with [`root_path` configuration option](#absolute-paths).
|
- If necessary, absolute paths can be validated too, with [`root_path` configuration option](#absolute-paths).
|
||||||
|
- Headings defined multiple times in one file, will get enumerated as `<heading><divider><count>`.
|
||||||
|
The divider can be customized with [`fragment-count-divider`](#divider-for-fragment-index).
|
||||||
|
|
||||||
### Limitations
|
### Limitations
|
||||||
|
|
||||||
@@ -128,6 +130,16 @@ After this change, all absolute paths will be converted to relative paths, and w
|
|||||||
|
|
||||||
For example, if you run markdownlint from a subdirectory (if `package.json` is located in a subdirectory), you should set `root_path` to `".."`.
|
For example, if you run markdownlint from a subdirectory (if `package.json` is located in a subdirectory), you should set `root_path` to `".."`.
|
||||||
|
|
||||||
|
### Divider for Fragment-Index
|
||||||
|
|
||||||
|
Headers with the same name in the same file, are appended with their index when converting them to the fragment.
|
||||||
|
Between the original fragment and the index a divider will be inserted.
|
||||||
|
The final fragment is `<original-fragment><divider><index>`.
|
||||||
|
|
||||||
|
This divider can be configured with `fragment-index-divider` to accomodate different markdown-engines.
|
||||||
|
|
||||||
|
The default-value is `-`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
Generated
+1174
-786
File diff suppressed because it is too large
Load Diff
+8
-8
@@ -36,20 +36,20 @@
|
|||||||
"release": "semantic-release"
|
"release": "semantic-release"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"markdown-it": "14.1.0",
|
"markdown-it": "14.2.0",
|
||||||
"mime": "4.1.0"
|
"mime": "4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/markdown-it": "14.1.2",
|
"@types/markdown-it": "14.1.2",
|
||||||
"@types/node": "25.2.1",
|
"@types/node": "25.9.2",
|
||||||
"eslint-config-conventions": "21.2.0",
|
"eslint-config-conventions": "21.4.0",
|
||||||
"markdownlint": "0.40.0",
|
"markdownlint": "0.40.0",
|
||||||
"markdownlint-cli2": "0.20.0",
|
"markdownlint-cli2": "0.22.1",
|
||||||
"oxfmt": "0.28.0",
|
"oxfmt": "0.54.0",
|
||||||
"oxlint": "1.43.0",
|
"oxlint": "1.69.0",
|
||||||
"oxlint-tsgolint": "0.11.4",
|
"oxlint-tsgolint": "0.23.0",
|
||||||
"semantic-release": "25.0.3",
|
"semantic-release": "25.0.3",
|
||||||
"typescript": "5.9.3"
|
"typescript": "6.0.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22.0.0"
|
"node": ">=22.0.0"
|
||||||
|
|||||||
+3
-1
@@ -144,12 +144,14 @@ const relativeLinksRule = {
|
|||||||
/** @type {Map<string, number>} */
|
/** @type {Map<string, number>} */
|
||||||
const fragments = new Map()
|
const fragments = new Map()
|
||||||
|
|
||||||
|
const fragmentCountDivider = params.config["fragment-index-divider"] ?? "-"
|
||||||
|
|
||||||
const fragmentsHTML = headings.map((heading) => {
|
const fragmentsHTML = headings.map((heading) => {
|
||||||
const fragment = convertHeadingToHTMLFragment(heading)
|
const fragment = convertHeadingToHTMLFragment(heading)
|
||||||
const count = fragments.get(fragment) ?? 0
|
const count = fragments.get(fragment) ?? 0
|
||||||
fragments.set(fragment, count + 1)
|
fragments.set(fragment, count + 1)
|
||||||
if (count !== 0) {
|
if (count !== 0) {
|
||||||
return `${fragment}-${count}`
|
return `${fragment}${fragmentCountDivider}${count}`
|
||||||
}
|
}
|
||||||
return fragment
|
return fragment
|
||||||
})
|
})
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ import { getHtmlAttributeRe } from "./markdownlint-rule-helpers/helpers.js"
|
|||||||
|
|
||||||
export const markdownIt = new MarkdownIt({ html: true })
|
export const markdownIt = new MarkdownIt({ html: true })
|
||||||
|
|
||||||
export const lineFragmentRe = /^#(?:L\d+(?:C\d+)?-L\d+(?:C\d+)?|L\d+)$/
|
export const lineFragmentRe = /^#(?:L\d+(?:C\d+)?-L\d+(?:C\d+)?|L\d+)$/u
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a Markdown heading into an HTML fragment according to the rules
|
* Converts a Markdown heading into an HTML fragment according to the rules
|
||||||
@@ -125,7 +125,7 @@ export const getMarkdownIdOrAnchorNameFragments = (content) => {
|
|||||||
* @example isValidIntegerString("1.0") // false
|
* @example isValidIntegerString("1.0") // false
|
||||||
*/
|
*/
|
||||||
export const isValidIntegerString = (value) => {
|
export const isValidIntegerString = (value) => {
|
||||||
const regex = /^\d+$/
|
const regex = /^\d+$/u
|
||||||
return regex.test(value)
|
return regex.test(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Awesome
|
||||||
|
|
||||||
|
## Existing Heading
|
||||||
|
|
||||||
|
### Repeated Heading
|
||||||
|
|
||||||
|
Text
|
||||||
|
|
||||||
|
### Repeated Heading
|
||||||
|
|
||||||
|
Text
|
||||||
|
|
||||||
|
### Repeated Heading
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
# Valid
|
||||||
|
|
||||||
|
[Link fragment](./awesome.md#existing-heading)
|
||||||
|
|
||||||
|
[Link fragment Repeated 0](./awesome.md#repeated-heading)
|
||||||
|
|
||||||
|
[Link fragment Repeated 1](./awesome.md#repeated-heading_1)
|
||||||
|
|
||||||
|
[Link fragment Repeated 2](./awesome.md#repeated-heading_2)
|
||||||
@@ -239,6 +239,16 @@ test("ensure the rule validates correctly", async (t) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "should be valid with multiple existing element id fragments",
|
||||||
|
fixturePath:
|
||||||
|
"test/fixtures/valid/existing-heading-fragment-divider/existing-heading-fragment.md",
|
||||||
|
config: {
|
||||||
|
"relative-links": {
|
||||||
|
"fragment-index-divider": "_",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "should ignore external image links",
|
name: "should ignore external image links",
|
||||||
fixturePath: "test/fixtures/valid/ignore-external-image.md",
|
fixturePath: "test/fixtures/valid/ignore-external-image.md",
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
"moduleResolution": "NodeNext",
|
"moduleResolution": "NodeNext",
|
||||||
|
"types": ["@types/node"],
|
||||||
"checkJs": true,
|
"checkJs": true,
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|||||||
Reference in New Issue
Block a user