Skip to content

Commit dfda7fa

Browse files
authored
feat: Add meta-charset-require rule (#1628)
1 parent b524170 commit dfda7fa

File tree

8 files changed

+143
-3
lines changed

8 files changed

+143
-3
lines changed

dist/core/rules/index.js

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/rules/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export { default as inlineScriptDisabled } from './inline-script-disabled'
2323
export { default as inlineStyleDisabled } from './inline-style-disabled'
2424
export { default as inputRequiresLabel } from './input-requires-label'
2525
export { default as mainRequire } from './main-require'
26+
export { default as metaCharsetRequire } from './meta-charset-require'
2627
export { default as metaDescriptionRequire } from './meta-description-require'
2728
export { default as metaViewportRequire } from './meta-viewport-require'
2829
export { default as scriptDisabled } from './script-disabled'
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Block, Listener } from '../htmlparser'
2+
import { Rule } from '../types'
3+
4+
export default {
5+
id: 'meta-charset-require',
6+
description: '<meta charset=""> must be present in <head> tag.',
7+
init(parser, reporter) {
8+
let headSeen = false
9+
let metaCharsetSeen = false
10+
let metaCharsetContent = ''
11+
let headEvent: Block | null = null
12+
13+
const onTagStart: Listener = (event) => {
14+
const tagName = event.tagName.toLowerCase()
15+
if (tagName === 'head') {
16+
headSeen = true
17+
headEvent = event
18+
} else if (tagName === 'meta') {
19+
const mapAttrs = parser.getMapAttrs(event.attrs)
20+
if (mapAttrs['charset'] !== undefined) {
21+
metaCharsetSeen = true
22+
metaCharsetContent = mapAttrs['charset'] || ''
23+
}
24+
}
25+
}
26+
27+
parser.addListener('tagstart', onTagStart)
28+
parser.addListener('end', () => {
29+
if (headSeen && headEvent) {
30+
if (!metaCharsetSeen) {
31+
reporter.error(
32+
'<meta charset=""> must be present in <head> tag.',
33+
headEvent.line,
34+
headEvent.col,
35+
this,
36+
headEvent.raw
37+
)
38+
} else if (metaCharsetContent.trim() === '') {
39+
reporter.error(
40+
'<meta charset=""> value must not be empty.',
41+
headEvent.line,
42+
headEvent.col,
43+
this,
44+
headEvent.raw
45+
)
46+
}
47+
}
48+
})
49+
},
50+
} as Rule
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
const HTMLHint = require('../../dist/htmlhint.js').HTMLHint
2+
const ruleId = 'meta-charset-require'
3+
4+
describe('Rule: meta-charset-require', () => {
5+
it('should not report an error when a valid meta charset is present', () => {
6+
const code = `<!DOCTYPE html><html><head><meta charset="utf-8"></head><body></body></html>`
7+
const messages = HTMLHint.verify(code, { [ruleId]: true })
8+
expect(messages.length).toBe(0)
9+
})
10+
11+
it('should report an error when meta charset is missing', () => {
12+
const code = `<!DOCTYPE html><html><head></head><body></body></html>`
13+
const messages = HTMLHint.verify(code, { [ruleId]: true })
14+
expect(messages.length).toBe(1)
15+
expect(messages[0].message).toBe(
16+
'<meta charset=""> must be present in <head> tag.'
17+
)
18+
})
19+
20+
it('should report an error when meta charset value is blank', () => {
21+
const code = `<!DOCTYPE html><html><head><meta charset=""></head><body></body></html>`
22+
const messages = HTMLHint.verify(code, { [ruleId]: true })
23+
expect(messages.length).toBe(1)
24+
expect(messages[0].message).toBe(
25+
'<meta charset=""> value must not be empty.'
26+
)
27+
})
28+
29+
it('should report an error when meta charset value is only whitespace', () => {
30+
const code = `<!DOCTYPE html><html><head><meta charset=" "></head><body></body></html>`
31+
const messages = HTMLHint.verify(code, { [ruleId]: true })
32+
expect(messages.length).toBe(1)
33+
expect(messages[0].message).toBe(
34+
'<meta charset=""> value must not be empty.'
35+
)
36+
})
37+
38+
it('should report an error when meta charset is missing and only other meta tags are present', () => {
39+
const code = `<!DOCTYPE html><html><head><meta name="description" content="desc"></head><body></body></html>`
40+
const messages = HTMLHint.verify(code, { [ruleId]: true })
41+
expect(messages.length).toBe(1)
42+
expect(messages[0].message).toBe(
43+
'<meta charset=""> must be present in <head> tag.'
44+
)
45+
})
46+
})

website/src/content/docs/list-rules.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
id: list-rules
33
title: List of rules
44
description: A complete list of all the rules for HTMLHint
5+
draft: true
56
---
67

78
## Doctype and Head
@@ -10,6 +11,7 @@ description: A complete list of all the rules for HTMLHint
1011
- [`doctype-html5`](/rules/doctype-html5): Invalid doctype.
1112
- [`head-script-disabled`](/rules/head-script-disabled): The `<script>` tag cannot be used in `<head>` tag.
1213
- [`html-lang-require`](/rules/html-lang-require): The HTML lang attribute is required.
14+
- [`meta-charset-require`](/rules/meta-charset-require): `<meta charset="">` must be present in `<head>` tag.
1315
- [`meta-description-require`](/rules/meta-description-require): `<meta name="description">` with non-blank content must be present in `<head>` tag.
1416
- [`meta-viewport-require`](/rules/meta-viewport-require): `<meta name="viewport">` with non-blank content must be present in `<head>` tag.
1517
- [`script-disabled`](/rules/script-disabled): `<script>` tags cannot be used.

website/src/content/docs/rules/index.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ description: A complete list of all the rules for HTMLHint
99
- [`doctype-html5`](doctype-html5/): Invalid doctype.
1010
- [`head-script-disabled`](head-script-disabled/): The `<script>` tag cannot be used in `<head>` tag.
1111
- [`html-lang-require`](html-lang-require/): The HTML lang attribute is required.
12+
- [`meta-charset-require`](meta-charset-require/): `<meta charset="">` must be present in `<head>` tag.
1213
- [`meta-description-require`](meta-description-require/): `<meta name="description">` with non-blank content must be present in `<head>` tag.
1314
- [`meta-viewport-require`](meta-viewport-require/): `<meta name="viewport">` with non-blank content must be present in `<head>` tag.
1415
- [`script-disabled`](script-disabled/): `<script>` tags cannot be used.

website/src/content/docs/rules/inline-script-disabled.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ Level: <Badge text="Warning" variant="caution" />
1515
1. true: enable rule
1616
2. false: disable rule
1717

18-
### The following pattern are considered violations
18+
### The following patterns are considered violations
1919

2020
```html
2121
<img src="test.gif" onclick="alert(1);">
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
id: meta-charset-require
3+
title: meta-charset-require
4+
description: Ensures every HTML document includes a meta charset tag within the head element for proper character encoding.
5+
sidebar:
6+
hidden: true
7+
badge: New
8+
---
9+
10+
import { Badge } from '@astrojs/starlight/components';
11+
12+
A `<meta charset="">` must be present in `<head>` tag.
13+
14+
Level: <Badge text="Error" variant="danger" />
15+
16+
## Config value
17+
18+
1. true: enable rule
19+
2. false: disable rule
20+
21+
### The following patterns are **not** considered rule violations
22+
23+
```html
24+
<html><head><meta charset="utf-8"></head></html>
25+
```
26+
27+
### The following patterns are considered rule violations
28+
29+
```html
30+
<!-- Missing meta charset -->
31+
<html><head></head></html>
32+
33+
<!-- Empty meta charset value -->
34+
<html><head><meta charset=""></head></html>
35+
36+
<!-- Whitespace-only meta charset value -->
37+
<html><head><meta charset=" "></head></html>
38+
```

0 commit comments

Comments
 (0)