--- /dev/null
+"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;