index.mjs 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import valueParser from 'postcss-value-parser';
  2. import { atRuleParamIndex, declarationValueIndex } from '../../utils/nodeFieldIndices.mjs';
  3. import { atRuleRegexes, propertyRegexes } from '../../utils/regexes.mjs';
  4. import { isRegExp, isString } from '../../utils/validateTypes.mjs';
  5. import { basicKeywords } from '../../reference/keywords.mjs';
  6. import isStandardSyntaxAtRule from '../../utils/isStandardSyntaxAtRule.mjs';
  7. import isStandardSyntaxDeclaration from '../../utils/isStandardSyntaxDeclaration.mjs';
  8. import isStandardSyntaxValue from '../../utils/isStandardSyntaxValue.mjs';
  9. import report from '../../utils/report.mjs';
  10. import ruleMessages from '../../utils/ruleMessages.mjs';
  11. import validateOptions from '../../utils/validateOptions.mjs';
  12. const ruleName = 'container-name-pattern';
  13. const messages = ruleMessages(ruleName, {
  14. expected: (containerName, pattern) => `Expected "${containerName}" to match pattern "${pattern}"`,
  15. });
  16. const meta = {
  17. url: 'https://stylelint.io/user-guide/rules/container-name-pattern',
  18. };
  19. const KEYWORDS = new Set(['and', 'or', 'none', 'not']);
  20. /** @type {import('stylelint').CoreRules[ruleName]} */
  21. const rule = (primary) => {
  22. return (root, result) => {
  23. const validOptions = validateOptions(result, ruleName, {
  24. actual: primary,
  25. possible: [isRegExp, isString],
  26. });
  27. if (!validOptions) return;
  28. const regex = isString(primary) ? new RegExp(primary) : primary;
  29. const languageCssWideKeywords =
  30. result.stylelint.config?.languageOptions?.syntax?.cssWideKeywords ?? [];
  31. const cssWideKeywords = new Set([...basicKeywords, ...languageCssWideKeywords]);
  32. root.walkDecls(propertyRegexes.containerNameAndShorthandName, (decl) => {
  33. if (!isStandardSyntaxDeclaration(decl)) return;
  34. const parsedValue = valueParser(decl.value);
  35. let isContainerType = false;
  36. parsedValue.walk(({ sourceIndex, type, value }) => {
  37. if (isContainerType) return;
  38. if (type === 'div' && value === '/') isContainerType = true;
  39. if (type !== 'word') return false;
  40. if (cssWideKeywords.has(value.toLowerCase())) return;
  41. if (!isStandardSyntaxValue(value)) return;
  42. if (regex.test(value)) return;
  43. complain(declarationValueIndex(decl) + sourceIndex, value, decl);
  44. });
  45. });
  46. root.walkAtRules(atRuleRegexes.containerName, (atRule) => {
  47. if (!isStandardSyntaxAtRule(atRule)) return;
  48. const { params } = atRule;
  49. const parsedValue = valueParser(params);
  50. parsedValue.walk(({ sourceIndex, type, value }) => {
  51. if (type !== 'word') return false;
  52. if (KEYWORDS.has(value.toLowerCase())) return;
  53. if (regex.test(value)) return;
  54. complain(atRuleParamIndex(atRule) + sourceIndex, value, atRule);
  55. });
  56. });
  57. /**
  58. * @param {number} index
  59. * @param {string} containerName
  60. * @param {import('postcss').Declaration|import('postcss').AtRule} node
  61. */
  62. function complain(index, containerName, node) {
  63. report({
  64. result,
  65. ruleName,
  66. message: messages.expected,
  67. messageArgs: [containerName, primary],
  68. node,
  69. index,
  70. endIndex: index + containerName.length,
  71. });
  72. }
  73. };
  74. };
  75. rule.ruleName = ruleName;
  76. rule.messages = messages;
  77. rule.meta = meta;
  78. export default rule;