index.mjs 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. import valueParser from 'postcss-value-parser';
  2. import { isRegExp, isString } from '../../utils/validateTypes.mjs';
  3. import { camelCaseFunctions } from '../../reference/functions.mjs';
  4. import { declarationValueIndex } from '../../utils/nodeFieldIndices.mjs';
  5. import getDeclarationValue from '../../utils/getDeclarationValue.mjs';
  6. import isStandardSyntaxFunction from '../../utils/isStandardSyntaxFunction.mjs';
  7. import isStandardSyntaxValue from '../../utils/isStandardSyntaxValue.mjs';
  8. import optionsMatches from '../../utils/optionsMatches.mjs';
  9. import report from '../../utils/report.mjs';
  10. import ruleMessages from '../../utils/ruleMessages.mjs';
  11. import setDeclarationValue from '../../utils/setDeclarationValue.mjs';
  12. import validateOptions from '../../utils/validateOptions.mjs';
  13. const ruleName = 'function-name-case';
  14. const messages = ruleMessages(ruleName, {
  15. expected: (actual, expected) => `Expected "${actual}" to be "${expected}"`,
  16. });
  17. const meta = {
  18. url: 'https://stylelint.io/user-guide/rules/function-name-case',
  19. fixable: true,
  20. };
  21. const mapLowercaseFunctionNamesToCamelCase = new Map();
  22. for (const func of camelCaseFunctions) {
  23. mapLowercaseFunctionNamesToCamelCase.set(func.toLowerCase(), func);
  24. }
  25. /** @type {import('stylelint').CoreRules[ruleName]} */
  26. const rule = (primary, secondaryOptions) => {
  27. return (root, result) => {
  28. const validOptions = validateOptions(
  29. result,
  30. ruleName,
  31. {
  32. actual: primary,
  33. possible: ['lower', 'upper'],
  34. },
  35. {
  36. actual: secondaryOptions,
  37. possible: {
  38. ignoreFunctions: [isString, isRegExp],
  39. },
  40. optional: true,
  41. },
  42. );
  43. if (!validOptions) {
  44. return;
  45. }
  46. root.walkDecls((decl) => {
  47. if (!decl.value.includes('(')) return;
  48. if (!isStandardSyntaxValue(decl.value)) return;
  49. const parsed = valueParser(getDeclarationValue(decl));
  50. parsed.walk((node) => {
  51. if (node.type !== 'function' || !isStandardSyntaxFunction(node)) {
  52. return;
  53. }
  54. const functionName = node.value;
  55. const functionNameLowerCase = functionName.toLowerCase();
  56. if (optionsMatches(secondaryOptions, 'ignoreFunctions', functionName)) {
  57. return;
  58. }
  59. /** @type {string} */
  60. let expectedFunctionName;
  61. if (
  62. primary === 'lower' &&
  63. mapLowercaseFunctionNamesToCamelCase.has(functionNameLowerCase)
  64. ) {
  65. expectedFunctionName = mapLowercaseFunctionNamesToCamelCase.get(functionNameLowerCase);
  66. } else if (primary === 'lower') {
  67. expectedFunctionName = functionNameLowerCase;
  68. } else {
  69. expectedFunctionName = functionName.toUpperCase();
  70. }
  71. if (functionName === expectedFunctionName) {
  72. return;
  73. }
  74. const index = declarationValueIndex(decl) + node.sourceIndex;
  75. const endIndex = index + functionName.length;
  76. const fix = () => {
  77. node.value = expectedFunctionName;
  78. setDeclarationValue(decl, parsed.toString());
  79. };
  80. report({
  81. message: messages.expected,
  82. messageArgs: [functionName, expectedFunctionName],
  83. node: decl,
  84. index,
  85. endIndex,
  86. result,
  87. ruleName,
  88. fix: {
  89. apply: fix,
  90. node: decl,
  91. },
  92. });
  93. });
  94. });
  95. };
  96. };
  97. rule.ruleName = ruleName;
  98. rule.messages = messages;
  99. rule.meta = meta;
  100. export default rule;