-
Notifications
You must be signed in to change notification settings - Fork 60.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add expired content linter rules (#49684)
Co-authored-by: hubwriter <[email protected]> Co-authored-by: Ethan Palm <[email protected]> Co-authored-by: Grace Park <[email protected]>
- Loading branch information
1 parent
0ee144f
commit 030d659
Showing
5 changed files
with
251 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { addError } from 'markdownlint-rule-helpers' | ||
|
||
// This rule looks for opening and closing HTML comment tags that | ||
// contain an expiration date in the format: | ||
// | ||
// This is content <!-- expires yyyy-mm-dd --> that is | ||
// expired <!-- end expires yyyy-mm-dd --> that does not expire. | ||
// | ||
// The `end expires` closing tag closes the content that is expired | ||
// and must be removed. | ||
export const expiredContent = { | ||
names: ['GHD038', 'expired-content'], | ||
description: 'Expired content must be remediated.', | ||
tags: ['expired'], | ||
function: (params, onError) => { | ||
const tokensToCheck = params.tokens.filter( | ||
(token) => token.type === 'inline' || token.type === 'html_block', | ||
) | ||
|
||
tokensToCheck.forEach((token) => { | ||
// Looking for just opening tag with format: | ||
// <!-- expires yyyy-mm-dd --> | ||
const match = token.content.match(/<!--\s*expires\s(\d\d\d\d)-(\d\d)-(\d\d)\s*-->/) | ||
if (!match) return | ||
|
||
const expireDate = new Date(match.splice(1, 3).join(' ')) | ||
const today = new Date() | ||
if (today < expireDate) return | ||
|
||
addError( | ||
onError, | ||
token.lineNumber, | ||
`Content marked with an expiration date has now expired. The content exists between 2 HTML comment tags in the format <!-- expires yyyy-mm-dd --> and <!-- end expires yyyy-mm-dd -->. You should remove or rewrite this content, and delete the expiration comments. Alternatively, choose a new expiration date.`, | ||
match[0], | ||
[token.content.indexOf(match[0]) + 1, match[0].length], | ||
null, // No fix possible | ||
) | ||
}) | ||
}, | ||
} | ||
|
||
export const DAYS_TO_WARN_BEFORE_EXPIRED = 14 | ||
|
||
// This rule looks for content that will expire in `DAYS_TO_WARN_BEFORE_EXPIRED` | ||
// days. The rule looks for opening and closing HTML comment tags that | ||
// contain an expiration date in the format: | ||
// | ||
// This is content <!-- expires yyyy-mm-dd --> that is | ||
// expired <!-- end expires yyyy-mm-dd --> that does not expire. | ||
// | ||
// The `end expires` closing tag closes the content that is expired | ||
// and must be removed. | ||
export const expiringSoon = { | ||
names: ['GHD039', 'expiring-soon'], | ||
description: 'Content that expires soon should be proactively addressed.', | ||
tags: ['expired'], | ||
function: (params, onError) => { | ||
const tokensToCheck = params.tokens.filter( | ||
(token) => token.type === 'inline' || token.type === 'html_block', | ||
) | ||
|
||
tokensToCheck.forEach((token) => { | ||
// Looking for just opening tag with format: | ||
// <!-- expires yyyy-mm-dd --> | ||
const match = token.content.match(/<!--\s*expires\s(\d\d\d\d)-(\d\d)-(\d\d)\s*-->/) | ||
if (!match) return | ||
|
||
const expireDate = new Date(match.splice(1, 3).join(' ')) | ||
const today = new Date() | ||
const futureDate = new Date() | ||
futureDate.setDate(today.getDate() + DAYS_TO_WARN_BEFORE_EXPIRED) | ||
// Don't set warning if the content is already expired or | ||
// if the content expires later than the DAYS_TO_WARN_BEFORE_EXPIRED | ||
if (today > expireDate || expireDate > futureDate) return | ||
|
||
addError( | ||
onError, | ||
token.lineNumber, | ||
`Content marked with an expiration date will expire soon. The content exists between 2 HTML comment tags in the format <!-- expires yyyy-mm-dd --> and <!-- end expires yyyy-mm-dd -->. Check whether this content can be removed or rewritten before it expires.`, | ||
match[0], | ||
[token.content.indexOf(match[0]) + 1, match[0].length], | ||
null, // No fix possible | ||
) | ||
}) | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
import { runRule } from '../../lib/init-test.js' | ||
import { | ||
expiredContent, | ||
expiringSoon, | ||
DAYS_TO_WARN_BEFORE_EXPIRED, | ||
} from '../../lib/linting-rules/expired-content.js' | ||
|
||
describe(expiredContent.names.join(' - '), () => { | ||
test('Date in the past triggers error', async () => { | ||
const markdown = [ | ||
'', | ||
'This is some content that <!-- expires 2024-03-01 -->is', | ||
'expiring soon<!-- end expires 2024-03-01 --> never expires.', | ||
].join('\n') | ||
const result = await runRule(expiredContent, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(1) | ||
expect(errors[0].lineNumber).toBe(2) | ||
expect(errors[0].errorRange).toEqual([27, 27]) | ||
expect(errors[0].fixInfo).toBeNull() | ||
}) | ||
|
||
test('Date in the future does not trigger error', async () => { | ||
const markdown = [ | ||
'', | ||
'This is some content that <!-- expires 2099-03-01 -->is', | ||
'expiring soon<!-- end expires 2024-03-01 --> never expires.', | ||
].join('\n') | ||
const result = await runRule(expiredContent, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(0) | ||
}) | ||
|
||
test('multiple spaces in HTML comment triggers error', async () => { | ||
const markdown = [ | ||
'', | ||
'This is some content that <!-- expires 2024-03-01 -->is', | ||
'expiring soon<!-- end expires 2024-03-01 --> never expires.', | ||
].join('\n') | ||
const result = await runRule(expiredContent, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(1) | ||
expect(errors[0].lineNumber).toBe(2) | ||
expect(errors[0].errorRange).toEqual([27, 34]) | ||
expect(errors[0].fixInfo).toBeNull() | ||
}) | ||
|
||
test('no surrounding spaces in HTML comment triggers error', async () => { | ||
const markdown = [ | ||
'', | ||
'This is some content that <!--expires 2024-03-01-->is', | ||
'expiring soon<!-- end expires 2024-03-01 --> never expires.', | ||
].join('\n') | ||
const result = await runRule(expiredContent, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(1) | ||
expect(errors[0].lineNumber).toBe(2) | ||
expect(errors[0].errorRange).toEqual([27, 25]) | ||
expect(errors[0].fixInfo).toBeNull() | ||
}) | ||
|
||
test('HTML tag on its own line triggers error', async () => { | ||
const markdown = [ | ||
'', | ||
'<!-- expires 2024-03-01 -->', | ||
`This is expiring soon.`, | ||
'<!-- end expires 2024-03-01 -->', | ||
].join('\n') | ||
const result = await runRule(expiredContent, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(1) | ||
expect(errors[0].lineNumber).toBe(2) | ||
expect(errors[0].errorRange).toEqual([1, 27]) | ||
expect(errors[0].fixInfo).toBeNull() | ||
}) | ||
}) | ||
|
||
describe(expiringSoon.names.join(' - '), () => { | ||
test('Date more than number of days to warn does not trigger warning', async () => { | ||
const formattedDate = getFormattedDate(1) | ||
const markdown = [ | ||
'', | ||
`This is some content that <!-- expires ${formattedDate} -->is`, | ||
`expiring soon<!-- end expires ${formattedDate} --> never expires.`, | ||
].join('\n') | ||
const result = await runRule(expiringSoon, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(0) | ||
}) | ||
|
||
test('Date equivalent to number of days to warn does trigger warning', async () => { | ||
const formattedDate = getFormattedDate() | ||
const markdown = [ | ||
'', | ||
`This is some content that <!-- expires ${formattedDate} -->is`, | ||
`expiring soon<!-- end expires ${formattedDate} --> never expires.`, | ||
].join('\n') | ||
const result = await runRule(expiringSoon, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(1) | ||
expect(errors[0].lineNumber).toBe(2) | ||
expect(errors[0].errorRange).toEqual([27, 27]) | ||
expect(errors[0].fixInfo).toBeNull() | ||
}) | ||
|
||
test('Date less than number of days to warn does trigger warning', async () => { | ||
const formattedDate = getFormattedDate(-1) | ||
const markdown = [ | ||
'', | ||
`This is some content that <!-- expires ${formattedDate} -->is`, | ||
`expiring soon<!-- end expires ${formattedDate} --> never expires.`, | ||
].join('\n') | ||
const result = await runRule(expiringSoon, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(1) | ||
expect(errors[0].lineNumber).toBe(2) | ||
expect(errors[0].errorRange).toEqual([27, 27]) | ||
expect(errors[0].fixInfo).toBeNull() | ||
}) | ||
test('HTML tag on its own line triggeres warning', async () => { | ||
const formattedDate = getFormattedDate(-1) | ||
const markdown = [ | ||
'', | ||
`<!-- expires ${formattedDate} -->`, | ||
`This is expiring soon.`, | ||
`<!-- end expires ${formattedDate}`, | ||
].join('\n') | ||
const result = await runRule(expiringSoon, { strings: { markdown } }) | ||
const errors = result.markdown | ||
expect(errors.length).toBe(1) | ||
expect(errors[0].lineNumber).toBe(2) | ||
expect(errors[0].errorRange).toEqual([1, 27]) | ||
expect(errors[0].fixInfo).toBeNull() | ||
}) | ||
}) | ||
|
||
function getFormattedDate(additionalDays = 0) { | ||
const today = new Date() | ||
today.setDate(today.getDate() + DAYS_TO_WARN_BEFORE_EXPIRED + additionalDays) | ||
return today | ||
.toLocaleDateString('en-GB', { | ||
year: 'numeric', | ||
month: '2-digit', | ||
day: '2-digit', | ||
}) | ||
.split('/') | ||
.reverse() | ||
.join('-') | ||
} |