index.js 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. 'use strict';
  2. const declarationValueIndex = require('../../utils/declarationValueIndex');
  3. const getDeclarationValue = require('../../utils/getDeclarationValue');
  4. const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction');
  5. const keywordSets = require('../../reference/keywordSets');
  6. const optionsMatches = require('../../utils/optionsMatches');
  7. const report = require('../../utils/report');
  8. const ruleMessages = require('../../utils/ruleMessages');
  9. const setDeclarationValue = require('../../utils/setDeclarationValue');
  10. const validateOptions = require('../../utils/validateOptions');
  11. const valueParser = require('postcss-value-parser');
  12. const { isRegExp, isString } = require('../../utils/validateTypes');
  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/list/function-name-case',
  19. };
  20. const mapLowercaseFunctionNamesToCamelCase = new Map();
  21. for (const func of keywordSets.camelCaseFunctionNames) {
  22. mapLowercaseFunctionNamesToCamelCase.set(func.toLowerCase(), func);
  23. }
  24. /** @type {import('stylelint').Rule} */
  25. const rule = (primary, secondaryOptions, context) => {
  26. return (root, result) => {
  27. const validOptions = validateOptions(
  28. result,
  29. ruleName,
  30. {
  31. actual: primary,
  32. possible: ['lower', 'upper'],
  33. },
  34. {
  35. actual: secondaryOptions,
  36. possible: {
  37. ignoreFunctions: [isString, isRegExp],
  38. },
  39. optional: true,
  40. },
  41. );
  42. if (!validOptions) {
  43. return;
  44. }
  45. root.walkDecls((decl) => {
  46. let needFix = false;
  47. const parsed = valueParser(getDeclarationValue(decl));
  48. parsed.walk((node) => {
  49. if (node.type !== 'function' || !isStandardSyntaxFunction(node)) {
  50. return;
  51. }
  52. const functionName = node.value;
  53. const functionNameLowerCase = functionName.toLowerCase();
  54. if (optionsMatches(secondaryOptions, 'ignoreFunctions', functionName)) {
  55. return;
  56. }
  57. let expectedFunctionName = null;
  58. if (
  59. primary === 'lower' &&
  60. mapLowercaseFunctionNamesToCamelCase.has(functionNameLowerCase)
  61. ) {
  62. expectedFunctionName = mapLowercaseFunctionNamesToCamelCase.get(functionNameLowerCase);
  63. } else if (primary === 'lower') {
  64. expectedFunctionName = functionNameLowerCase;
  65. } else {
  66. expectedFunctionName = functionName.toUpperCase();
  67. }
  68. if (functionName === expectedFunctionName) {
  69. return;
  70. }
  71. if (context.fix) {
  72. needFix = true;
  73. node.value = expectedFunctionName;
  74. return;
  75. }
  76. report({
  77. message: messages.expected(functionName, expectedFunctionName),
  78. node: decl,
  79. index: declarationValueIndex(decl) + node.sourceIndex,
  80. result,
  81. ruleName,
  82. });
  83. });
  84. if (context.fix && needFix) {
  85. setDeclarationValue(decl, parsed.toString());
  86. }
  87. });
  88. };
  89. };
  90. rule.ruleName = ruleName;
  91. rule.messages = messages;
  92. rule.meta = meta;
  93. module.exports = rule;