Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dist/core/htmlparser.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions dist/core/rules/index.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/core/htmlparser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default class HTMLParser {

const regTag =
// eslint-disable-next-line no-control-regex
/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s+[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g
/<(?:\/([^\s>]+)\s*|!--([\s\S]*?)--|!([^>]*?)|([\w\-:]+)((?:\s*[^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'>]*))?)*?)\s*(\/?))>/g

Check failure

Code scanning / CodeQL

Bad HTML filtering regexp

Comments ending with --> are matched differently from comments ending with --!>. The first is matched with capture group 2 and comments ending with --!> are matched with capture group 3.

Check failure

Code scanning / CodeQL

Inefficient regular expression

This part of the regular expression may cause exponential backtracking on strings starting with '<-' and containing many repetitions of '!'.
const regAttr =
// eslint-disable-next-line no-control-regex
/\s*([^\s"'>\/=\x00-\x0F\x7F\x80-\x9F]+)(?:\s*=\s*(?:(")([^"]*)"|(')([^']*)'|([^\s"'>]*)))?/g
Expand Down
23 changes: 23 additions & 0 deletions src/core/rules/attr-space-between.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Rule } from '../types'

export default {
id: 'attr-space-between',
description: 'Attributes must be separated by a space.',
init(parser, reporter) {
parser.addListener('tagstart', (event) => {
const col = event.col + event.tagName.length + 1

for (const { index, name, raw } of event.attrs) {
if (!raw.match(/^\s/)) {
reporter.error(
`Attribute [ ${name} ] must be separated with a space.`,
event.line,
col + index,
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 @@ -4,6 +4,7 @@ export { default as attrNoDuplication } from './attr-no-duplication'
export { default as attrNoUnnecessaryWhitespace } from './attr-no-unnecessary-whitespace'
export { default as attrValueNoDuplication } from './attr-value-no-duplication'
export { default as attrSort } from './attr-sorted'
export { default as attrSpaceBetween } from './attr-space-between'
export { default as attrUnsafeChars } from './attr-unsafe-chars'
export { default as attrValueDoubleQuotes } from './attr-value-double-quotes'
export { default as attrValueNotEmpty } from './attr-value-not-empty'
Expand Down
1 change: 1 addition & 0 deletions src/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export interface Ruleset {
'attr-no-duplication'?: boolean
'attr-no-unnecessary-whitespace'?: boolean
'attr-sorted'?: boolean
'attr-space-between'?: boolean
'attr-unsafe-chars'?: boolean
'attr-value-double-quotes'?: boolean
'attr-value-not-empty'?: boolean
Expand Down
43 changes: 43 additions & 0 deletions test/rules/attr-space-between.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
const HTMLHint = require('../../dist/htmlhint.js').HTMLHint

const ruleId = 'attr-space-between'
const ruleOptions = {}

ruleOptions[ruleId] = true

describe(`Rules: ${ruleId}`, () => {
it('Attributes without a space between them should result in an error', () => {
const code = '<div id="foo"class="bar"></div>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(1)
expect(messages[0].rule.id).toBe(ruleId)
expect(messages[0].message).toBe(
'Attribute [ class ] must be separated with a space.'
)
})

it('Multiple attributes without spaces should each result in an error', () => {
const code = '<div id="foo"class="bar"title="baz"></div>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(2)
messages.forEach((msg) => expect(msg.rule.id).toBe(ruleId))
})

it('Attributes separated by spaces should not result in an error', () => {
const code = '<div id="foo" class="bar"></div>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(0)
})

it('Unquoted and valueless attributes should not result in an error', () => {
const code = '<input type=checkbox checked>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(0)
})

it('Attributes separated by newlines should not result in an error', () => {
const code = '<div id="foo"\n class="bar"></div>'
const messages = HTMLHint.verify(code, ruleOptions)
expect(messages.length).toBe(0)
})
})
39 changes: 39 additions & 0 deletions website/src/content/docs/rules/attr-space-between.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
id: attr-space-between
title: attr-space-between
description: Attributes must be separated by a space.
sidebar:
badge: New
---

import { Badge } from '@astrojs/starlight/components';

Attributes must be separated by a space.

Level: <Badge text="Error" variant="danger" />

## Config value

- `true`: enable rule
- `false`: disable rule

## Description

Without a space between attributes, browsers recover from the markup, but the
HTMLHint parser interprets the tag as text — which produces misleading errors
from other rules (such as `spec-char-escape` and `tag-pair`) instead of
pointing at the real problem. This rule reports the missing space directly.

### The following patterns are **not** considered rule violations

<!-- prettier-ignore -->
```html
<div class="foo" id="bar"></div>
```

### The following patterns are considered rule violations:

<!-- prettier-ignore -->
```html
<div class="foo"id="bar"></div>
```
1 change: 1 addition & 0 deletions website/src/content/docs/rules/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ description: A complete list of all the rules for HTMLHint
- [`attr-no-duplication`](attr-no-duplication/): Elements cannot have duplicate attributes.
- [`attr-no-unnecessary-whitespace`](attr-no-unnecessary-whitespace/): No spaces between attribute names and values.
- [`attr-sorted`](attr-sorted/): Attributes should be sorted in order.
- [`attr-space-between`](attr-space-between/): Attributes must be separated by a space.
- [`attr-unsafe-chars`](attr-unsafe-chars/): Attribute values cannot contain unsafe chars.
- [`attr-value-double-quotes`](attr-value-double-quotes/): Attribute values must be in double quotes.
- [`attr-value-no-duplication`](attr-value-no-duplication/): Attribute values should not contain duplicates.
Expand Down