"use strict"; const _ = require("lodash"); const declarationValueIndex = require("../../utils/declarationValueIndex"); const isStandardSyntaxFunction = require("../../utils/isStandardSyntaxFunction"); const isStandardSyntaxValue = require("../../utils/isStandardSyntaxValue"); const keywordSets = require("../../reference/keywordSets"); const namedColorDataHex = require("../../reference/namedColorData"); const optionsMatches = require("../../utils/optionsMatches"); const propertySets = require("../../reference/propertySets"); const report = require("../../utils/report"); const ruleMessages = require("../../utils/ruleMessages"); const validateOptions = require("../../utils/validateOptions"); const valueParser = require("postcss-value-parser"); const generateColorFuncs = require("./generateColorFuncs"); const ruleName = "color-named"; const messages = ruleMessages(ruleName, { expected: (named, original) => `Expected "${original}" to be "${named}"`, rejected: named => `Unexpected named color "${named}"` }); // Todo tested on case insensivity const NODE_TYPES = ["word", "function"]; const rule = function(expectation, options) { return (root, result) => { const validOptions = validateOptions( result, ruleName, { actual: expectation, possible: ["never", "always-where-possible"] }, { actual: options, possible: { ignoreProperties: [_.isString], ignore: ["inside-function"] }, optional: true } ); if (!validOptions) { return; } const namedColors = Object.keys(namedColorDataHex); const namedColorData = {}; namedColors.forEach(name => { const hex = namedColorDataHex[name]; namedColorData[name] = { hex, func: generateColorFuncs(hex[0]) }; }); root.walkDecls(decl => { if (propertySets.acceptCustomIdents.has(decl.prop)) { return; } // Return early if the property is to be ignored if (optionsMatches(options, "ignoreProperties", decl.prop)) { return; } valueParser(decl.value).walk(node => { const value = node.value, type = node.type, sourceIndex = node.sourceIndex; if ( optionsMatches(options, "ignore", "inside-function") && type === "function" ) { return false; } if (!isStandardSyntaxFunction(node)) { return false; } if (!isStandardSyntaxValue(value)) { return; } // Return early if neither a word nor a function if (NODE_TYPES.indexOf(type) === -1) { return; } // Check for named colors for "never" option if ( expectation === "never" && type === "word" && namedColors.indexOf(value.toLowerCase()) !== -1 ) { complain( messages.rejected(value), decl, declarationValueIndex(decl) + sourceIndex ); return; } // Check "always-where-possible" option ... if (expectation !== "always-where-possible") { return; } // First by checking for alternative color function representations ... if ( type === "function" && keywordSets.colorFunctionNames.has(value.toLowerCase()) ) { // Remove all spaces to match what's in `representations` const normalizedFunctionString = valueParser .stringify(node) .replace(/\s+/g, ""); let namedColor; for (let i = 0, l = namedColors.length; i < l; i++) { namedColor = namedColors[i]; if ( namedColorData[namedColor].func.indexOf( normalizedFunctionString.toLowerCase() ) !== -1 ) { complain( messages.expected(namedColor, normalizedFunctionString), decl, declarationValueIndex(decl) + sourceIndex ); return; // Exit as soon as a problem is found } } return; } // Then by checking for alternative hex representations let namedColor; for (let i = 0, l = namedColors.length; i < l; i++) { namedColor = namedColors[i]; if ( namedColorData[namedColor].hex.indexOf(value.toLowerCase()) !== -1 ) { complain( messages.expected(namedColor, value), decl, declarationValueIndex(decl) + sourceIndex ); return; // Exit as soon as a problem is found } } }); }); function complain(message, node, index) { report({ result, ruleName, message, node, index }); } }; }; rule.ruleName = ruleName; rule.messages = messages; module.exports = rule;