Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
291 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
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,57 @@ | ||
# no-irregular-whitespace | ||
|
||
Disallow irregular whitespaces. | ||
|
||
<!-- prettier-ignore --> | ||
```css | ||
.firstClass .secondClass {} | ||
/** ↑ | ||
* Irregular whitespace. Selector would fail to match '.firstClass' */ | ||
``` | ||
|
||
## Options | ||
|
||
### `true` | ||
|
||
The following patterns are considered violations: | ||
|
||
<!-- prettier-ignore --> | ||
```css | ||
.firstClass .secondClass {} | ||
``` | ||
|
||
The following patterns are _not_ considered violations: | ||
|
||
<!-- prettier-ignore --> | ||
```css | ||
.firstClass .secondClass { /* Writing comments with irregular whitespaces */ } | ||
``` | ||
|
||
## Unicode reference of irregular whitespaces | ||
|
||
``` | ||
\u000B - Line Tabulation (\v) - <VT> | ||
\u000C - Form Feed (\f) - <FF> | ||
\u00A0 - No-Break Space - <NBSP> | ||
\u0085 - Next Line | ||
\u1680 - Ogham Space Mark | ||
\u180E - Mongolian Vowel Separator - <MVS> | ||
\uFEFF - Zero Width No-Break Space - <BOM> | ||
\u2000 - En Quad | ||
\u2001 - Em Quad | ||
\u2002 - En Space - <ENSP> | ||
\u2003 - Em Space - <EMSP> | ||
\u2004 - Tree-Per-Em | ||
\u2005 - Four-Per-Em | ||
\u2006 - Six-Per-Em | ||
\u2007 - Figure Space | ||
\u2008 - Punctuation Space - <PUNCSP> | ||
\u2009 - Thin Space | ||
\u200A - Hair Space | ||
\u200B - Zero Width Space - <ZWSP> | ||
\u2028 - Line Separator | ||
\u2029 - Paragraph Separator | ||
\u202F - Narrow No-Break Space | ||
\u205F - Medium Mathematical Space | ||
\u3000 - Ideographic Space | ||
``` |
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,98 @@ | ||
'use strict'; | ||
|
||
const { messages, ruleName } = require('..'); | ||
|
||
const IRREGULAR_WHITESPACES = [ | ||
'\u000B', // Line Tabulation (\v) - <VT> | ||
'\u000C', // Form Feed (\f) - <FF> | ||
'\u00A0', // No-Break Space - <NBSP> | ||
'\u0085', // Next Line | ||
'\u1680', // Ogham Space Mark | ||
'\u180E', // Mongolian Vowel Separator - <MVS> | ||
'\uFEFF', // Zero Width No-Break Space - <BOM> | ||
'\u2000', // En Quad | ||
'\u2001', // Em Quad | ||
'\u2002', // En Space - <ENSP> | ||
'\u2003', // Em Space - <EMSP> | ||
'\u2004', // Tree-Per-Em | ||
'\u2005', // Four-Per-Em | ||
'\u2006', // Six-Per-Em | ||
'\u2007', // Figure Space | ||
'\u2008', // Punctuation Space - <PUNCSP> | ||
'\u2009', // Thin Space | ||
'\u200A', // Hair Space | ||
'\u200B', // Zero Width Space - <ZWSP> | ||
'\u2028', // Line Separator | ||
'\u2029', // Paragraph Separator | ||
'\u202F', // Narrow No-Break Space | ||
'\u205F', // Medium Mathematical Space | ||
'\u3000', // Ideographic Space | ||
]; | ||
|
||
const characterToUnicodeString = (str) => | ||
`\\u${str.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}`; | ||
|
||
testRule({ | ||
ruleName, | ||
config: true, | ||
accept: [ | ||
{ | ||
code: ' ', | ||
description: 'regular whitespace', | ||
}, | ||
{ | ||
code: '\n', | ||
description: 'new line', | ||
}, | ||
{ | ||
code: '/* Irregular whitespace\nin multi line comments\nare allowed */', | ||
description: 'irregular whitespace in multi line comments', | ||
}, | ||
], | ||
|
||
reject: [ | ||
{ | ||
code: '.firstClass .secondClass { color: pink; }', | ||
description: 'irregular whitespace in selector', | ||
message: messages.unexpected, | ||
line: 1, | ||
column: 12, | ||
}, | ||
{ | ||
code: '.firstClass .secondClass { color: pink; }', | ||
description: 'irregular whitespace after selector', | ||
message: messages.unexpected, | ||
line: 1, | ||
column: 26, | ||
}, | ||
{ | ||
code: 'margin: 1rem 2rem;', | ||
description: 'irregular whitespace in declaration value', | ||
message: messages.unexpected, | ||
line: 1, | ||
column: 13, | ||
}, | ||
{ | ||
code: 'margin: 1rem 2rem ;', | ||
description: 'irregular whitespace after value', | ||
message: messages.unexpected, | ||
line: 1, | ||
column: 18, | ||
}, | ||
{ | ||
code: '$variable : 5rem;', | ||
description: 'irregular whitespace in variable name', | ||
message: messages.unexpected, | ||
line: 1, | ||
column: 10, | ||
}, | ||
// Generic test for all types of irregular whitespaces. | ||
...IRREGULAR_WHITESPACES.map((ws) => ({ | ||
code: `a[title="irregular${ws}whitespace"] { color: pink; }`, | ||
description: `irregular whitespace in attribute selector: ${characterToUnicodeString(ws)}`, | ||
message: messages.unexpected, | ||
line: 1, | ||
column: 19, | ||
})), | ||
], | ||
}); |
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,134 @@ | ||
// @ts-nocheck | ||
|
||
'use strict'; | ||
|
||
const report = require('../../utils/report'); | ||
const ruleMessages = require('../../utils/ruleMessages'); | ||
const validateOptions = require('../../utils/validateOptions'); | ||
|
||
const ruleName = 'no-irregular-whitespace'; | ||
const messages = ruleMessages(ruleName, { | ||
unexpected: 'Unexpected irregular whitespace', | ||
}); | ||
|
||
const IRREGULAR_WHITESPACES = [ | ||
'\u000B', // Line Tabulation (\v) - <VT> | ||
'\u000C', // Form Feed (\f) - <FF> | ||
'\u00A0', // No-Break Space - <NBSP> | ||
'\u0085', // Next Line | ||
'\u1680', // Ogham Space Mark | ||
'\u180E', // Mongolian Vowel Separator - <MVS> | ||
'\uFEFF', // Zero Width No-Break Space - <BOM> | ||
'\u2000', // En Quad | ||
'\u2001', // Em Quad | ||
'\u2002', // En Space - <ENSP> | ||
'\u2003', // Em Space - <EMSP> | ||
'\u2004', // Tree-Per-Em | ||
'\u2005', // Four-Per-Em | ||
'\u2006', // Six-Per-Em | ||
'\u2007', // Figure Space | ||
'\u2008', // Punctuation Space - <PUNCSP> | ||
'\u2009', // Thin Space | ||
'\u200A', // Hair Space | ||
'\u200B', // Zero Width Space - <ZWSP> | ||
'\u2028', // Line Separator | ||
'\u2029', // Paragraph Separator | ||
'\u202F', // Narrow No-Break Space | ||
'\u205F', // Medium Mathematical Space | ||
'\u3000', // Ideographic Space | ||
]; | ||
|
||
const IRREGULAR_WHITESPACES_PATTERN = new RegExp(`([${IRREGULAR_WHITESPACES.join('')}])`); | ||
|
||
const generateInvalidWhitespaceValidator = () => { | ||
return (str) => typeof str === 'string' && IRREGULAR_WHITESPACES_PATTERN.exec(str); | ||
}; | ||
|
||
const declarationSchema = { | ||
prop: 'string', | ||
value: 'string', | ||
raws: { | ||
before: 'string', | ||
between: 'string', | ||
}, | ||
}; | ||
|
||
const atRuleSchema = { | ||
name: 'string', | ||
params: 'string', | ||
raws: { | ||
before: 'string', | ||
between: 'string', | ||
afterName: 'string', | ||
after: 'string', | ||
}, | ||
}; | ||
|
||
const ruleSchema = { | ||
selector: 'string', | ||
raws: { | ||
before: 'string', | ||
between: 'string', | ||
after: 'string', | ||
}, | ||
}; | ||
|
||
const generateNodeValidator = (nodeSchema, validator) => { | ||
const allKeys = Object.keys(nodeSchema); | ||
const validatorForKey = {}; | ||
|
||
allKeys.forEach((key) => { | ||
if (typeof nodeSchema[key] === 'string') validatorForKey[key] = validator; | ||
|
||
if (typeof nodeSchema[key] === 'object') | ||
validatorForKey[key] = generateNodeValidator(nodeSchema[key], validator); | ||
}); | ||
|
||
// This will be called many times, so it's optimized for performance and not readibility. | ||
// Surprisingly, this seem to be slightly faster then concatenating the params and running the validator once. | ||
return (node) => { | ||
for (const currentKey of allKeys) { | ||
if (validatorForKey[currentKey](node[currentKey])) { | ||
return validatorForKey[currentKey](node[currentKey]); | ||
} | ||
} | ||
}; | ||
}; | ||
|
||
function rule(on) { | ||
return (root, result) => { | ||
const validOptions = validateOptions(result, ruleName, { actual: on }); | ||
|
||
if (!validOptions) { | ||
return; | ||
} | ||
|
||
const genericValidator = generateInvalidWhitespaceValidator(); | ||
|
||
const validate = (node, validator) => { | ||
const issue = validator(node); | ||
|
||
if (issue) { | ||
report({ | ||
ruleName, | ||
result, | ||
message: messages.unexpected, | ||
node, | ||
word: issue[1], | ||
}); | ||
} | ||
}; | ||
|
||
const atRuleValidator = generateNodeValidator(atRuleSchema, genericValidator); | ||
const ruleValidator = generateNodeValidator(ruleSchema, genericValidator); | ||
const declValidator = generateNodeValidator(declarationSchema, genericValidator); | ||
|
||
root.walkAtRules((atRule) => validate(atRule, atRuleValidator)); | ||
root.walkRules((selector) => validate(selector, ruleValidator)); | ||
root.walkDecls((declaration) => validate(declaration, declValidator)); | ||
}; | ||
} | ||
|
||
rule.ruleName = ruleName; | ||
rule.messages = messages; | ||
module.exports = rule; |