forked from ember-template-lint/ember-template-lint
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add
no-whitespace-within-word
rule (ember-template-lint#848)
- Loading branch information
Showing
5 changed files
with
282 additions
and
0 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
## no-whitespace-within-word | ||
|
||
In practice, the predominant issue raised by inline whitespace styling is that the resultant text 'formatting' is entirely visual in nature; the ability to discern the correct manner in which to read the text, and therefore, to correctly comprehend its meaning, is restricted to sighted users. | ||
|
||
Using in-line whitespace word formatting produces results that are explicitly mentioned in [WCAG's list of common sources of web accessibility failures](https://www.w3.org/TR/WCAG20-TECHS/failures.html). Specifically, this common whitespace-within-word-induced web accessibility issue fails to successfully achieve [WCAG Success Criterion 1.3.2: Meaningful Sequence](https://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-sequence.html). | ||
|
||
The `no-whitespace-within-word` rule operates on the assumption that artifically-spaced English words in rendered text content contain, at a minimum, two word characters fencepost-delimited by three whitespace characters (`space-char-space-char-space`) so it should be avoided. | ||
|
||
### Examples | ||
|
||
This rule **forbids** the following: | ||
|
||
```hbs | ||
W e l c o m e | ||
``` | ||
|
||
`W`**` `**`e`**` `**`l`**` `**`c`**` `**`o`**` `**`m`**` `**`e` | ||
|
||
`Wel c o me` | ||
|
||
`Wel`**` `**`c`**` `**`o`**` `**`me` | ||
|
||
```hbs | ||
<div>W e l c o m e</div> | ||
<div>Wel c o me</div> | ||
``` | ||
|
||
This rule **allows** the following: | ||
|
||
`Welcome` | ||
|
||
`Yes`**` `**`I`**` `**`am` | ||
|
||
`It is possible to get some examples of in-word emph a sis past this rule.` | ||
|
||
`However, I do not want a rule that flags annoying false positives for correctly-used single-character words.` | ||
|
||
```hbs | ||
<div>Welcome</div> | ||
<div>Yes I am.</div> | ||
``` | ||
|
||
This rule uses the heuristic of letter, whitespace character, letter, whitespace character, letter which makes it a good candidate for most use cases, but not ideal for some languages (such as Japanese). | ||
|
||
### Migration | ||
|
||
Use CSS to add letter-spacing to a word. | ||
|
||
### References | ||
|
||
* [F32: Using white space characters to create multiple columns in plain text content](https://www.w3.org/TR/WCAG20-TECHS/failures.html#F32) | ||
* [WCAG Success Criterion 1.3.2: Meaningful Sequence](https://www.w3.org/TR/UNDERSTANDING-WCAG20/content-structure-separation-sequence.html) | ||
* [C8: Using CSS letter-spacing to control spacing within a word](https://www.w3.org/WAI/WCAG21/Techniques/css/C8) |
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,143 @@ | ||
'use strict'; | ||
const Rule = require('./base'); | ||
|
||
const ERROR_MESSAGE = 'Excess whitespace in layout detected.'; | ||
|
||
const blackList = [ | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
' ', | ||
'  ', | ||
'​', | ||
'​', | ||
'​', | ||
'​', | ||
'​', | ||
'​', | ||
'‌', | ||
'‌', | ||
'‍', | ||
'‍', | ||
'‎', | ||
'‎', | ||
'‏', | ||
'‏', | ||
' ', | ||
' ', | ||
'  ', | ||
'⁠', | ||
'⁠', | ||
'⁡', | ||
'⁡', | ||
'⁡', | ||
'⁢', | ||
'⁢', | ||
'⁢', | ||
'⁣', | ||
'⁣', | ||
'⁣', | ||
]; | ||
|
||
function isWhitespace(char) { | ||
return blackList.includes(char); | ||
} | ||
|
||
function splitTextByEntity(input) { | ||
let result = []; | ||
|
||
for (let i = 0; i < input.length; i++) { | ||
let current = input[i]; | ||
|
||
if (current === '&') { | ||
let possibleEndIndex = input.indexOf(';', i); | ||
|
||
// this is a stand alone `&` | ||
if (possibleEndIndex === -1) { | ||
result.push(current); | ||
} | ||
|
||
// now we know we have an "entity like thing" | ||
let possibleEntity = input.substring(i, possibleEndIndex + 1); | ||
if (blackList.includes(possibleEntity)) { | ||
result.push(possibleEntity); | ||
i += possibleEntity.length - 1; | ||
} else { | ||
result.push(current); | ||
} | ||
} else { | ||
result.push(current); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
// The goal here is to catch alternating non-whitespace/whitespace | ||
// characters, for example, in 'W e l c o m e'. | ||
// | ||
// So the final pattern boils down to this: | ||
// | ||
// (whitespace)(non-whitespace)(whitespace)(non-whitespace)(whitespace) | ||
// | ||
// Specifically using this "5 alternations" rule since any less than this | ||
// will return false positives and any more than this should not be | ||
// necessary in 99.99% of cases | ||
module.exports = class NoWhitespaceWithinWord extends Rule { | ||
visitor() { | ||
return { | ||
TextNode(node) { | ||
let alternationCount = 0; | ||
let source = this.sourceForNode(node); | ||
let characters = splitTextByEntity(source); | ||
|
||
for (let i = 0; i < characters.length; i++) { | ||
let currentChar = characters[i]; | ||
let previousChar = i > 0 ? characters[i - 1] : undefined; | ||
|
||
if ( | ||
(isWhitespace(currentChar) && !isWhitespace(previousChar)) || | ||
(!isWhitespace(currentChar) && isWhitespace(previousChar)) | ||
) { | ||
alternationCount++; | ||
} else { | ||
alternationCount = 0; | ||
} | ||
|
||
if (alternationCount >= 5) { | ||
this.log({ | ||
message: ERROR_MESSAGE, | ||
line: node.loc && node.loc.start.line, | ||
column: node.loc && node.loc.start.column, | ||
source, | ||
}); | ||
|
||
// no need to keep parsing, we've already reported | ||
return; | ||
} | ||
} | ||
}, | ||
}; | ||
} | ||
}; | ||
|
||
module.exports.ERROR_MESSAGE = ERROR_MESSAGE; |
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,82 @@ | ||
// no-whitespace-within-word-test.js | ||
|
||
'use strict'; | ||
|
||
const generateRuleTests = require('../../helpers/rule-test-harness'); | ||
const ERROR_MESSAGE = require('../../../lib/rules/lint-no-whitespace-within-word').ERROR_MESSAGE; | ||
|
||
generateRuleTests({ | ||
name: 'no-whitespace-within-word', | ||
config: true, | ||
|
||
good: [ | ||
'Welcome', | ||
`It is possible to get some examples of in-word emph a sis past this rule.`, | ||
`However, I do not want a rule that flags annoying false positives for correctly-used single-character words.`, | ||
'<div>Welcome</div>', | ||
], | ||
|
||
bad: [ | ||
{ | ||
template: 'W e l c o m e', | ||
|
||
result: { | ||
moduleId: 'layout.hbs', | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 0, | ||
source: 'W e l c o m e', | ||
}, | ||
}, | ||
{ | ||
template: 'W e l c o m e', | ||
result: { | ||
moduleId: 'layout.hbs', | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 0, | ||
source: 'W e l c o m e', | ||
}, | ||
}, | ||
{ | ||
template: 'Wel c o me', | ||
result: { | ||
moduleId: 'layout.hbs', | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 0, | ||
source: 'Wel c o me', | ||
}, | ||
}, | ||
{ | ||
template: 'Wel c o me', | ||
result: { | ||
moduleId: 'layout.hbs', | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 0, | ||
source: 'Wel c o me', | ||
}, | ||
}, | ||
{ | ||
template: '<div>W e l c o m e</div>', | ||
result: { | ||
moduleId: 'layout.hbs', | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 5, | ||
source: 'W e l c o m e', | ||
}, | ||
}, | ||
{ | ||
template: '<div>Wel c o me</div>', | ||
result: { | ||
moduleId: 'layout.hbs', | ||
message: ERROR_MESSAGE, | ||
line: 1, | ||
column: 5, | ||
source: 'Wel c o me', | ||
}, | ||
}, | ||
], | ||
}); |