| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164 |
- 'use strict';
- const report = require('../../utils/report');
- const ruleMessages = require('../../utils/ruleMessages');
- const transformSelector = require('../../utils/transformSelector');
- const validateOptions = require('../../utils/validateOptions');
- const { assertString } = require('../../utils/validateTypes');
- const ruleName = 'keyframe-selector-notation';
- const messages = ruleMessages(ruleName, {
- expected: (selector, fixedSelector) => `Expected "${selector}" to be "${fixedSelector}"`,
- });
- const meta = {
- url: 'https://stylelint.io/user-guide/rules/keyframe-selector-notation',
- fixable: true,
- };
- const PERCENTAGE_SELECTORS = new Set(['0%', '100%']);
- const KEYWORD_SELECTORS = new Set(['from', 'to']);
- const NAMED_TIMELINE_RANGE_SELECTORS = new Set(['cover', 'contain', 'entry', 'enter', 'exit']);
- const PERCENTAGE_TO_KEYWORD = new Map([
- ['0%', 'from'],
- ['100%', 'to'],
- ]);
- const KEYWORD_TO_PERCENTAGE = new Map([
- ['from', '0%'],
- ['to', '100%'],
- ]);
- /** @type {import('stylelint').Rule<'keyword' | 'percentage' | 'percentage-unless-within-keyword-only-block'>} */
- const rule = (primary, _, context) => {
- return (root, result) => {
- const validOptions = validateOptions(result, ruleName, {
- actual: primary,
- possible: ['keyword', 'percentage', 'percentage-unless-within-keyword-only-block'],
- });
- if (!validOptions) return;
- /**
- * @typedef {{
- * expFunc: (selector: string, selectorsInBlock: string[]) => boolean,
- * fixFunc: (selector: string) => string,
- * }} OptionFuncs
- *
- * @type {Record<primary, OptionFuncs>}
- */
- const optionFuncs = Object.freeze({
- keyword: {
- expFunc: (selector) => KEYWORD_SELECTORS.has(selector),
- fixFunc: (selector) => getFromMap(PERCENTAGE_TO_KEYWORD, selector),
- },
- percentage: {
- expFunc: (selector) => PERCENTAGE_SELECTORS.has(selector),
- fixFunc: (selector) => getFromMap(KEYWORD_TO_PERCENTAGE, selector),
- },
- 'percentage-unless-within-keyword-only-block': {
- expFunc: (selector, selectorsInBlock) => {
- if (selectorsInBlock.every((s) => KEYWORD_SELECTORS.has(s))) return true;
- return PERCENTAGE_SELECTORS.has(selector);
- },
- fixFunc: (selector) => getFromMap(KEYWORD_TO_PERCENTAGE, selector),
- },
- });
- root.walkAtRules(/^(-(moz|webkit)-)?keyframes$/i, (atRuleKeyframes) => {
- const selectorsInBlock =
- primary === 'percentage-unless-within-keyword-only-block'
- ? getSelectorsInBlock(atRuleKeyframes)
- : [];
- atRuleKeyframes.walkRules((keyframeRule) => {
- transformSelector(result, keyframeRule, (selectors) => {
- let first = true;
- selectors.walkTags((selectorTag) => {
- if (first && NAMED_TIMELINE_RANGE_SELECTORS.has(selectorTag.value)) {
- return false;
- }
- first = false;
- checkSelector(
- selectorTag.value,
- optionFuncs[primary],
- (fixedSelector) => (selectorTag.value = fixedSelector),
- );
- });
- });
- /**
- * @param {string} selector
- * @param {OptionFuncs} funcs
- * @param {(fixedSelector: string) => void} fixer
- */
- function checkSelector(selector, { expFunc, fixFunc }, fixer) {
- const normalizedSelector = selector.toLowerCase();
- if (
- !KEYWORD_SELECTORS.has(normalizedSelector) &&
- !PERCENTAGE_SELECTORS.has(normalizedSelector)
- ) {
- return;
- }
- if (expFunc(selector, selectorsInBlock)) return;
- const fixedSelector = fixFunc(selector);
- if (context.fix) {
- fixer(fixedSelector);
- return;
- }
- report({
- message: messages.expected,
- messageArgs: [selector, fixedSelector],
- node: keyframeRule,
- result,
- ruleName,
- word: selector,
- });
- }
- });
- });
- };
- };
- /**
- * @param {Map<string, string>} map
- * @param {string} key
- * @returns {string}
- */
- function getFromMap(map, key) {
- const value = map.get(key);
- assertString(value);
- return value;
- }
- /**
- * @param {import('postcss').AtRule} atRule
- * @returns {string[]}
- */
- function getSelectorsInBlock(atRule) {
- /** @type {string[]} */
- const selectors = [];
- atRule.walkRules((r) => {
- selectors.push(...r.selectors);
- });
- return selectors;
- }
- rule.ruleName = ruleName;
- rule.messages = messages;
- rule.meta = meta;
- module.exports = rule;
|