@@ -34,6 +34,7 @@ We are using the [markdownlint](https://github.com/DavidAnson/markdownlint) fram
|
||||
| [MD113](./linting-rules/internal-links-slash.js) | Internal links must start with a `/`. | error |
|
||||
| [MD114](./linting-rules/internal-links-lang.js) | Internal links must not have a hardcoded language code. | error |
|
||||
| [MD115](./linting-rules/image-file-kebab.js) | Image file names should be lowercase kebab case. | error |
|
||||
| [MD117](./linting-rules/code-fence-line-length.js) | Code fence content should be 60 lines or less in length. | warning |
|
||||
|
||||
## Linting Tests
|
||||
|
||||
|
||||
8
src/content-linter/lib/init-test.js
Normal file
8
src/content-linter/lib/init-test.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import markdownlint from 'markdownlint'
|
||||
|
||||
import { testOptions } from './default-markdownlint-options.js'
|
||||
|
||||
export async function runRule(module, fixtureFile) {
|
||||
const options = testOptions(module.names[0], module, fixtureFile)
|
||||
return await markdownlint.promises.markdownlint(options)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { addError } from 'markdownlint-rule-helpers'
|
||||
|
||||
import { getCodeFenceTokens, getCodeFenceLines } from '../markdownlint-helpers.js'
|
||||
|
||||
export const codeFenceLineLength = {
|
||||
names: ['MD117', 'code-fence-line-length'],
|
||||
description: 'Code fence lines should not exceed a maximum length',
|
||||
tags: ['code'],
|
||||
severity: 'warning',
|
||||
function: function MD117(params, onError) {
|
||||
const MAX_LINE_LENGTH = String(params.config.maxLength || 60)
|
||||
const codeFenceTokens = getCodeFenceTokens(params)
|
||||
codeFenceTokens.forEach((token) => {
|
||||
const lines = getCodeFenceLines(token)
|
||||
lines.forEach((line, index) => {
|
||||
if (line.length > MAX_LINE_LENGTH) {
|
||||
// The token line number is the line number of the first line of the
|
||||
// code fence. We want to report the line number of the content within
|
||||
// the code fence so we need to add 1 + the index.
|
||||
const lineNumber = token.lineNumber + index + 1
|
||||
addError(
|
||||
onError,
|
||||
lineNumber,
|
||||
`Code fence line exceeds ${MAX_LINE_LENGTH} characters.`,
|
||||
undefined, // N/A
|
||||
undefined, // N/A
|
||||
undefined, // N/A
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
}
|
||||
9
src/content-linter/lib/markdownlint-helpers.js
Normal file
9
src/content-linter/lib/markdownlint-helpers.js
Normal file
@@ -0,0 +1,9 @@
|
||||
import { newLineRe } from 'markdownlint-rule-helpers'
|
||||
|
||||
export function getCodeFenceTokens(params) {
|
||||
return params.tokens.filter((t) => t.type === 'fence')
|
||||
}
|
||||
|
||||
export function getCodeFenceLines(token) {
|
||||
return token.content.split(newLineRe)
|
||||
}
|
||||
@@ -85,6 +85,7 @@ async function main() {
|
||||
MD113: true,
|
||||
MD114: true,
|
||||
MD115: true,
|
||||
MD117: true,
|
||||
}
|
||||
|
||||
const files = walkFiles(path, ['.md'], { includeBasePath: true })
|
||||
|
||||
31
src/content-linter/tests/fixtures/code-fence-line-length.md
vendored
Normal file
31
src/content-linter/tests/fixtures/code-fence-line-length.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
# Heading
|
||||
|
||||
Line length exceeds max length by 1
|
||||
|
||||
```shell
|
||||
111
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
bbb
|
||||
```
|
||||
|
||||
Line length equals max length
|
||||
|
||||
```shell
|
||||
111
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
bbbbbbb
|
||||
```
|
||||
|
||||
Line length is less than max length
|
||||
|
||||
```shell
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
```
|
||||
|
||||
Multiple lines in code fence exceed max length by 1
|
||||
|
||||
```shell
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaccc
|
||||
1
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbb
|
||||
```
|
||||
29
src/content-linter/tests/unit/code-fence-line-length.js
Normal file
29
src/content-linter/tests/unit/code-fence-line-length.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import { jest } from '@jest/globals'
|
||||
|
||||
import { runRule } from '../../lib/init-test.js'
|
||||
import { codeFenceLineLength } from '../../lib/linting-rules/code-fence-line-length.js'
|
||||
|
||||
jest.setTimeout(60 * 1000)
|
||||
|
||||
const fixtureFilePath = 'src/content-linter/tests/fixtures/code-fence-line-length.md'
|
||||
const result = await runRule(codeFenceLineLength, fixtureFilePath)
|
||||
const errors = result[fixtureFilePath]
|
||||
|
||||
describe(codeFenceLineLength.names.join(' - '), () => {
|
||||
test('line length of max length + 1 fails', async () => {
|
||||
expect(errors.map((error) => error.lineNumber).includes(7)).toBe(true)
|
||||
})
|
||||
test('line length equals max length passes', async () => {
|
||||
expect(errors.map((error) => error.lineNumber).includes(15)).toBe(false)
|
||||
})
|
||||
test('line length less than max length passes', async () => {
|
||||
expect(errors.map((error) => error.lineNumber).includes(22)).toBe(false)
|
||||
})
|
||||
test('multiple lines in code block that exceed max length fail', async () => {
|
||||
expect(errors.map((error) => error.lineNumber).includes(28)).toBe(true)
|
||||
expect(errors.map((error) => error.lineNumber).includes(30)).toBe(true)
|
||||
})
|
||||
test('errors only occur on expected lines', async () => {
|
||||
expect(errors.length).toBe(3)
|
||||
})
|
||||
})
|
||||
@@ -1,18 +1,18 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import markdownlint from 'markdownlint'
|
||||
|
||||
import { runRule } from '../../lib/init-test.js'
|
||||
import { imageAltTextEndPunctuation } from '../../lib/linting-rules/image-alt-text-end-punctuation.js'
|
||||
import { testOptions } from '../../lib/default-markdownlint-options.js'
|
||||
|
||||
jest.setTimeout(60 * 1000)
|
||||
|
||||
const fixtureFile = 'src/content-linter/tests/fixtures/image-alt-text-end-punctuation.md'
|
||||
const options = testOptions('MD112', imageAltTextEndPunctuation, fixtureFile)
|
||||
const result = await markdownlint.promises.markdownlint(options)
|
||||
const result = await runRule(imageAltTextEndPunctuation, fixtureFile)
|
||||
const errors = result[fixtureFile]
|
||||
|
||||
test('image alt text must have an end punctuation', () => {
|
||||
const errors = result[fixtureFile]
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(2)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([3, 15])
|
||||
describe(imageAltTextEndPunctuation.names.join(' - '), () => {
|
||||
test('image alt text must have an end punctuation', () => {
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(2)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([3, 15])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import markdownlint from 'markdownlint'
|
||||
|
||||
import { runRule } from '../../lib/init-test.js'
|
||||
import { incorrectAltTextLength } from '../../lib/linting-rules/image-alt-text-length.js'
|
||||
import { testOptions } from '../../lib/default-markdownlint-options.js'
|
||||
|
||||
jest.setTimeout(60 * 1000)
|
||||
|
||||
const fixtureFile = 'src/content-linter/tests/fixtures/image-alt-text-length.md'
|
||||
const options = testOptions('MD111', incorrectAltTextLength, fixtureFile)
|
||||
const result = await markdownlint.promises.markdownlint(options)
|
||||
const result = await runRule(incorrectAltTextLength, fixtureFile)
|
||||
const errors = result[fixtureFile]
|
||||
|
||||
test('image with correct length alt text', () => {
|
||||
const errors = result[fixtureFile]
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(2)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([1, 7])
|
||||
describe(incorrectAltTextLength.names.join(' - '), () => {
|
||||
test('image with correct length alt text', () => {
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(2)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([1, 7])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import markdownlint from 'markdownlint'
|
||||
|
||||
import { runRule } from '../../lib/init-test.js'
|
||||
import { imageFileKebab } from '../../lib/linting-rules/image-file-kebab'
|
||||
import { testOptions } from '../../lib/default-markdownlint-options.js'
|
||||
|
||||
jest.setTimeout(20 * 1000)
|
||||
|
||||
const fixtureFile = 'src/content-linter/tests/fixtures/image-file-kebab.md'
|
||||
const options = testOptions('MD115', imageFileKebab, fixtureFile)
|
||||
const result = await markdownlint.promises.markdownlint(options)
|
||||
const result = await runRule(imageFileKebab, fixtureFile)
|
||||
const errors = result[fixtureFile]
|
||||
|
||||
test('image file with lowercase kebab case', () => {
|
||||
const errors = result[fixtureFile]
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(4)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([4, 5, 6, 7])
|
||||
describe(imageFileKebab.names.join(' - '), () => {
|
||||
test('image file with lowercase kebab case', () => {
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(4)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([4, 5, 6, 7])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import markdownlint from 'markdownlint'
|
||||
|
||||
import { runRule } from '../../lib/init-test.js'
|
||||
import { internalLinksLang } from '../../lib/linting-rules/internal-links-lang.js'
|
||||
import { testOptions } from '../../lib/default-markdownlint-options.js'
|
||||
|
||||
const fixtureFile = 'src/content-linter/tests/fixtures/internal-links-lang.md'
|
||||
|
||||
jest.setTimeout(30 * 1000)
|
||||
const options = testOptions('MD114', internalLinksLang, fixtureFile)
|
||||
const fixtureFilePath = 'src/content-linter/tests/fixtures/internal-links-lang.md'
|
||||
const result = await runRule(internalLinksLang, fixtureFilePath)
|
||||
const errors = result[fixtureFilePath]
|
||||
|
||||
const result = await markdownlint.promises.markdownlint(options)
|
||||
test('internal links and hardcoded language codes', () => {
|
||||
const errors = result[fixtureFile]
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(3)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([3, 4, 8])
|
||||
describe(internalLinksLang.names.join(' - '), () => {
|
||||
test('internal links and hardcoded language codes', () => {
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(3)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([3, 4, 8])
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import { jest } from '@jest/globals'
|
||||
import markdownlint from 'markdownlint'
|
||||
|
||||
import { runRule } from '../../lib/init-test.js'
|
||||
import { internalLinksSlash } from '../../lib/linting-rules/internal-links-slash.js'
|
||||
import { testOptions } from '../../lib/default-markdownlint-options.js'
|
||||
|
||||
jest.setTimeout(60 * 1000)
|
||||
|
||||
const fixtureFile = 'src/content-linter/tests/fixtures/internal-links-slash.md'
|
||||
const options = testOptions('MD113', internalLinksSlash, fixtureFile)
|
||||
const result = await markdownlint.promises.markdownlint(options)
|
||||
const result = await runRule(internalLinksSlash, fixtureFile)
|
||||
const errors = result[fixtureFile]
|
||||
|
||||
test('relative links start with /', () => {
|
||||
const errors = result[fixtureFile]
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([5])
|
||||
describe(internalLinksSlash.names.join(' - '), () => {
|
||||
test('relative links start with /', () => {
|
||||
expect(Object.keys(result).length).toBe(1)
|
||||
expect(errors.length).toBe(1)
|
||||
expect(errors.map((error) => error.lineNumber)).toEqual([5])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user