Skip to content

Commit

Permalink
feat(rules): add html-lang-require rule (#632)
Browse files Browse the repository at this point in the history
* added html-lang-require rule
* add tests for html-lang-require rule

Co-authored-by: David Dias <thedaviddias@gmail.com>
  • Loading branch information
baleyko and thedaviddias committed Jun 10, 2021
1 parent 628e1f2 commit 51471a9
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 0 deletions.
66 changes: 66 additions & 0 deletions src/core/rules/html-lang-require.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { Rule } from '../types'

const regular =
'(art-lojban|cel-gaulish|no-bok|no-nyn|zh-guoyu|zh-hakka|zh-min|zh-min-nan|zh-xiang)'
const irregular =
'(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)'
const grandfathered = `(?<grandfathered>${irregular}|${regular})`
const privateUse = '(?<privateUse>x(-[A-Za-z0-9]{1,8})+)'
const privateUse2 = '(?<privateUse2>x(-[A-Za-z0-9]{1,8})+)'
const singleton = '[0-9A-WY-Za-wy-z]'
const extension = `(?<extension>${singleton}(-[A-Za-z0-9]{2,8})+)`
const variant = '(?<variant>[A-Za-z0-9]{5,8}|[0-9][A-Za-z0-9]{3})'
const region = '(?<region>[A-Za-z]{2}|[0-9]{3})'
const script = '(?<script>[A-Za-z]{4})'
const extlang = '(?<extlang>[A-Za-z]{3}(-[A-Za-z]{3}){0,2})'
const language = `(?<language>([A-Za-z]{2,3}(-${extlang})?)|[A-Za-z]{4}|[A-Za-z]{5,8})`
const langtag =
`(${language}(-${script})?` +
`(-${region})?` +
`(-${variant})*` +
`(-${extension})*` +
`(-${privateUse})?` +
')'
const languageTag = `(${grandfathered}|${langtag}|${privateUse2})`
const LANG_VALIDITY_PATTERN = new RegExp(languageTag, 'g')

export default {
id: 'html-lang-require',
description:
'The lang attribute of an <html> element must be present and should be valid.',
init(parser, reporter) {
parser.addListener('tagstart', (event) => {
const tagName = event.tagName.toLowerCase()
const mapAttrs = parser.getMapAttrs(event.attrs)
const col = event.col + tagName.length + 1

if (tagName === 'html' && 'lang' in mapAttrs) {
if (!mapAttrs['lang']) {
reporter.warn(
'The lang attribute of <html> element must have a value.',
event.line,
col,
this,
event.raw
)
} else if (!LANG_VALIDITY_PATTERN.test(mapAttrs['lang'])) {
reporter.warn(
'The lang attribute value of <html> element must be a valid BCP47.',
event.line,
col,
this,
event.raw
)
}
} else {
reporter.warn(
'An lang attribute must be present on <html> elements.',
event.line,
col,
this,
event.raw
)
}
})
},
} as Rule
1 change: 1 addition & 0 deletions src/core/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export { default as doctypeFirst } from './doctype-first'
export { default as doctypeHTML5 } from './doctype-html5'
export { default as headScriptDisabled } from './head-script-disabled'
export { default as hrefAbsOrRel } from './href-abs-or-rel'
export { default as htmlLangRequire } from './html-lang-require'
export { default as idClsasAdDisabled } from './id-class-ad-disabled'
export { default as idClassValue } from './id-class-value'
export { default as idUnique } from './id-unique'
Expand Down
31 changes: 31 additions & 0 deletions test/rules/html-lang-require.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const expect = require('expect.js')

const HTMLHint = require('../../dist/htmlhint.js').HTMLHint

const ruldId = 'html-lang-require'
const ruleOptions = {}

ruleOptions[ruldId] = true

describe(`Rules: ${ruldId}`, () => {
it('HTML tag have no a lang attribute should result in an error', () => {
const code = '<html></html>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).to.be(1)
})
it('HTML tag have an empty lang attribute should result in an error', () => {
const code = '<html lang=""></html>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).to.be(1)
})
it('HTML tag have an invalid lang attribute should result in an error', () => {
const code = '<html lang="-"></html>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).to.be(1)
})
it('HTML tag have an non emtpy and valid lang attribute should not result in an error', () => {
const code = '<html lang="en-EN"></html>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).to.be(0)
})
})

0 comments on commit 51471a9

Please sign in to comment.