| "use strict"; |
| |
| const _ = require("lodash"); |
| const declarationValueIndex = require("../../utils/declarationValueIndex"); |
| const getUnitFromValueNode = require("../../utils/getUnitFromValueNode"); |
| const isCounterIncrementCustomIdentValue = require("../../utils/isCounterIncrementCustomIdentValue"); |
| const isCounterResetCustomIdentValue = require("../../utils/isCounterResetCustomIdentValue"); |
| const isStandardSyntaxValue = require("../../utils/isStandardSyntaxValue"); |
| const keywordSets = require("../../reference/keywordSets"); |
| const matchesStringOrRegExp = require("../../utils/matchesStringOrRegExp"); |
| const report = require("../../utils/report"); |
| const ruleMessages = require("../../utils/ruleMessages"); |
| const validateOptions = require("../../utils/validateOptions"); |
| const valueParser = require("postcss-value-parser"); |
| |
| const ruleName = "value-keyword-case"; |
| |
| const messages = ruleMessages(ruleName, { |
| expected: (actual, expected) => `Expected "${actual}" to be "${expected}"` |
| }); |
| |
| // Operators are interpreted as "words" by the value parser, so we want to make sure to ignore them. |
| const ignoredCharacters = new Set(["+", "-", "/", "*", "%"]); |
| |
| const mapLowercaseKeywordsToCamelCase = new Map(); |
| |
| keywordSets.camelCaseKeywords.forEach(func => { |
| mapLowercaseKeywordsToCamelCase.set(func.toLowerCase(), func); |
| }); |
| |
| const rule = function(expectation, options, context) { |
| return (root, result) => { |
| const validOptions = validateOptions( |
| result, |
| ruleName, |
| { |
| actual: expectation, |
| possible: ["lower", "upper"] |
| }, |
| { |
| actual: options, |
| possible: { |
| ignoreProperties: [_.isString, _.isRegExp], |
| ignoreKeywords: [_.isString, _.isRegExp] |
| }, |
| optional: true |
| } |
| ); |
| |
| if (!validOptions) { |
| return; |
| } |
| |
| root.walkDecls(decl => { |
| const prop = decl.prop; |
| const value = decl.value; |
| |
| const parsed = valueParser( |
| decl.raws.value ? decl.raws.value.raw : decl.value |
| ); |
| |
| let needFix = false; |
| |
| parsed.walk(node => { |
| const valueLowerCase = node.value.toLowerCase(); |
| |
| // Ignore system colors |
| if (keywordSets.systemColors.has(valueLowerCase)) { |
| return; |
| } |
| |
| // Ignore keywords within `url` and `var` function |
| if ( |
| node.type === "function" && |
| (valueLowerCase === "url" || |
| valueLowerCase === "var" || |
| valueLowerCase === "counter" || |
| valueLowerCase === "counters" || |
| valueLowerCase === "attr") |
| ) { |
| return false; |
| } |
| |
| const keyword = node.value; |
| |
| // Ignore css variables, and hex values, and math operators, and sass interpolation |
| if ( |
| node.type !== "word" || |
| !isStandardSyntaxValue(node.value) || |
| value.indexOf("#") !== -1 || |
| ignoredCharacters.has(keyword) || |
| getUnitFromValueNode(node) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "animation" && |
| !keywordSets.animationShorthandKeywords.has(valueLowerCase) && |
| !keywordSets.animationNameKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "animation-name" && |
| !keywordSets.animationNameKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "font" && |
| !keywordSets.fontShorthandKeywords.has(valueLowerCase) && |
| !keywordSets.fontFamilyKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "font-family" && |
| !keywordSets.fontFamilyKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "counter-increment" && |
| isCounterIncrementCustomIdentValue(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "counter-reset" && |
| isCounterResetCustomIdentValue(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "grid-row" && |
| !keywordSets.gridRowKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "grid-column" && |
| !keywordSets.gridColumnKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "grid-area" && |
| !keywordSets.gridAreaKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "list-style" && |
| !keywordSets.listStyleShorthandKeywords.has(valueLowerCase) && |
| !keywordSets.listStyleTypeKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| if ( |
| prop === "list-style-type" && |
| !keywordSets.listStyleTypeKeywords.has(valueLowerCase) |
| ) { |
| return; |
| } |
| |
| const ignoreKeywords = (options && options.ignoreKeywords) || []; |
| const ignoreProperties = (options && options.ignoreProperties) || []; |
| |
| if ( |
| ignoreKeywords.length > 0 && |
| matchesStringOrRegExp(keyword, ignoreKeywords) |
| ) { |
| return; |
| } |
| |
| if ( |
| ignoreProperties.length > 0 && |
| matchesStringOrRegExp(prop, ignoreProperties) |
| ) { |
| return; |
| } |
| |
| const keywordLowerCase = keyword.toLocaleLowerCase(); |
| let expectedKeyword = null; |
| |
| if ( |
| expectation === "lower" && |
| mapLowercaseKeywordsToCamelCase.has(keywordLowerCase) |
| ) { |
| expectedKeyword = mapLowercaseKeywordsToCamelCase.get( |
| keywordLowerCase |
| ); |
| } else if (expectation === "lower") { |
| expectedKeyword = keyword.toLowerCase(); |
| } else { |
| expectedKeyword = keyword.toUpperCase(); |
| } |
| |
| if (keyword === expectedKeyword) { |
| return; |
| } |
| |
| if (context.fix) { |
| needFix = true; |
| node.value = expectedKeyword; |
| |
| return; |
| } |
| |
| report({ |
| message: messages.expected(keyword, expectedKeyword), |
| node: decl, |
| index: declarationValueIndex(decl) + node.sourceIndex, |
| result, |
| ruleName |
| }); |
| }); |
| |
| if (context.fix && needFix) { |
| decl.value = parsed.toString(); |
| } |
| }); |
| }; |
| }; |
| |
| rule.ruleName = ruleName; |
| rule.messages = messages; |
| module.exports = rule; |