blob: b9f636df0b73480e3feba43b3338c693dead99c6 [file] [log] [blame]
// @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 = {};
for (const key of allKeys) {
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;