Skip to content

Commit 51471a9

Browse files
baleykothedaviddias
andauthoredJun 10, 2021
feat(rules): add html-lang-require rule (#632)
* added html-lang-require rule * add tests for html-lang-require rule Co-authored-by: David Dias <thedaviddias@gmail.com>
1 parent 628e1f2 commit 51471a9

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed
 

‎src/core/rules/html-lang-require.ts

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { Rule } from '../types'
2+
3+
const regular =
4+
'(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)'
5+
const irregular =
6+
'(en-GB-oed|i-ami|i-bnn|i-default|i-enochian|i-hak|i-klingon|i-lux|i-mingo|i-navajo|i-pwn|i-tao|i-tay|i-tsu|sgn-BE-FR|sgn-BE-NL|sgn-CH-DE)'
7+
const grandfathered = `(?<grandfathered>${irregular}|${regular})`
8+
const privateUse = '(?<privateUse>x(-[A-Za-z0-9]{1,8})+)'
9+
const privateUse2 = '(?<privateUse2>x(-[A-Za-z0-9]{1,8})+)'
10+
const singleton = '[0-9A-WY-Za-wy-z]'
11+
const extension = `(?<extension>${singleton}(-[A-Za-z0-9]{2,8})+)`
12+
const variant = '(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})'
13+
const region = '(?<region>[A-Za-z]{2}|[0-9]{3})'
14+
const script = '(?<script>[A-Za-z]{4})'
15+
const extlang = '(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2})'
16+
const language = `(?<language>([A-Za-z]{2,3}(-${extlang})?)|[A-Za-z]{4}|[A-Za-z]{5,8})`
17+
const langtag =
18+
`(${language}(-${script})?` +
19+
`(-${region})?` +
20+
`(-${variant})*` +
21+
`(-${extension})*` +
22+
`(-${privateUse})?` +
23+
')'
24+
const languageTag = `(${grandfathered}|${langtag}|${privateUse2})`
25+
const LANG_VALIDITY_PATTERN = new RegExp(languageTag, 'g')
26+
27+
export default {
28+
id: 'html-lang-require',
29+
description:
30+
'The lang attribute of an <html> element must be present and should be valid.',
31+
init(parser, reporter) {
32+
parser.addListener('tagstart', (event) => {
33+
const tagName = event.tagName.toLowerCase()
34+
const mapAttrs = parser.getMapAttrs(event.attrs)
35+
const col = event.col + tagName.length + 1
36+
37+
if (tagName === 'html' && 'lang' in mapAttrs) {
38+
if (!mapAttrs['lang']) {
39+
reporter.warn(
40+
'The lang attribute of <html> element must have a value.',
41+
event.line,
42+
col,
43+
this,
44+
event.raw
45+
)
46+
} else if (!LANG_VALIDITY_PATTERN.test(mapAttrs['lang'])) {
47+
reporter.warn(
48+
'The lang attribute value of <html> element must be a valid BCP47.',
49+
event.line,
50+
col,
51+
this,
52+
event.raw
53+
)
54+
}
55+
} else {
56+
reporter.warn(
57+
'An lang attribute must be present on <html> elements.',
58+
event.line,
59+
col,
60+
this,
61+
event.raw
62+
)
63+
}
64+
})
65+
},
66+
} as Rule

‎src/core/rules/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export { default as doctypeFirst } from './doctype-first'
1111
export { default as doctypeHTML5 } from './doctype-html5'
1212
export { default as headScriptDisabled } from './head-script-disabled'
1313
export { default as hrefAbsOrRel } from './href-abs-or-rel'
14+
export { default as htmlLangRequire } from './html-lang-require'
1415
export { default as idClsasAdDisabled } from './id-class-ad-disabled'
1516
export { default as idClassValue } from './id-class-value'
1617
export { default as idUnique } from './id-unique'

‎test/rules/html-lang-require.spec.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const expect = require('expect.js')
2+
3+
const HTMLHint = require('../../dist/htmlhint.js').HTMLHint
4+
5+
const ruldId = 'html-lang-require'
6+
const ruleOptions = {}
7+
8+
ruleOptions[ruldId] = true
9+
10+
describe(`Rules: ${ruldId}`, () => {
11+
it('HTML tag have no a lang attribute should result in an error', () => {
12+
const code = '<html></html>'
13+
const messages = HTMLHint.verify(code, ruleOptions)
14+
expect(messages.length).to.be(1)
15+
})
16+
it('HTML tag have an empty lang attribute should result in an error', () => {
17+
const code = '<html lang=""></html>'
18+
const messages = HTMLHint.verify(code, ruleOptions)
19+
expect(messages.length).to.be(1)
20+
})
21+
it('HTML tag have an invalid lang attribute should result in an error', () => {
22+
const code = '<html lang="-"></html>'
23+
const messages = HTMLHint.verify(code, ruleOptions)
24+
expect(messages.length).to.be(1)
25+
})
26+
it('HTML tag have an non emtpy and valid lang attribute should not result in an error', () => {
27+
const code = '<html lang="en-EN"></html>'
28+
const messages = HTMLHint.verify(code, ruleOptions)
29+
expect(messages.length).to.be(0)
30+
})
31+
})

0 commit comments

Comments
 (0)
Please sign in to comment.