123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- 'use strict';
- const atRuleParamIndex = require('../../utils/atRuleParamIndex');
- const declarationValueIndex = require('../../utils/declarationValueIndex');
- const getDeclarationValue = require('../../utils/getDeclarationValue');
- const isWhitespace = require('../../utils/isWhitespace');
- const report = require('../../utils/report');
- const ruleMessages = require('../../utils/ruleMessages');
- const setDeclarationValue = require('../../utils/setDeclarationValue');
- const styleSearch = require('style-search');
- const validateOptions = require('../../utils/validateOptions');
- const ruleName = 'function-whitespace-after';
- const messages = ruleMessages(ruleName, {
- expected: 'Expected whitespace after ")"',
- rejected: 'Unexpected whitespace after ")"',
- });
- const meta = {
- url: 'https://stylelint.io/user-guide/rules/list/function-whitespace-after',
- };
- const ACCEPTABLE_AFTER_CLOSING_PAREN = new Set([')', ',', '}', ':', '/', undefined]);
- /** @type {import('stylelint').Rule} */
- const rule = (primary, _secondaryOptions, context) => {
- return (root, result) => {
- const validOptions = validateOptions(result, ruleName, {
- actual: primary,
- possible: ['always', 'never'],
- });
- if (!validOptions) {
- return;
- }
- /**
- * @param {import('postcss').Node} node
- * @param {string} value
- * @param {number} nodeIndex
- * @param {((index: number) => void) | undefined} fix
- */
- function check(node, value, nodeIndex, fix) {
- styleSearch(
- {
- source: value,
- target: ')',
- functionArguments: 'only',
- },
- (match) => {
- checkClosingParen(value, match.startIndex + 1, node, nodeIndex, fix);
- },
- );
- }
- /**
- * @param {string} source
- * @param {number} index
- * @param {import('postcss').Node} node
- * @param {number} nodeIndex
- * @param {((index: number) => void) | undefined} fix
- */
- function checkClosingParen(source, index, node, nodeIndex, fix) {
- const nextChar = source[index];
- if (primary === 'always') {
- // Allow for the next character to be a single empty space,
- // another closing parenthesis, a comma, or the end of the value
- if (nextChar === ' ') {
- return;
- }
- if (nextChar === '\n') {
- return;
- }
- if (source.slice(index, index + 2) === '\r\n') {
- return;
- }
- if (ACCEPTABLE_AFTER_CLOSING_PAREN.has(nextChar)) {
- return;
- }
- if (fix) {
- fix(index);
- return;
- }
- report({
- message: messages.expected,
- node,
- index: nodeIndex + index,
- result,
- ruleName,
- });
- } else if (primary === 'never' && isWhitespace(nextChar)) {
- if (fix) {
- fix(index);
- return;
- }
- report({
- message: messages.rejected,
- node,
- index: nodeIndex + index,
- result,
- ruleName,
- });
- }
- }
- /**
- * @param {string} value
- */
- function createFixer(value) {
- let fixed = '';
- let lastIndex = 0;
- /** @type {(index: number) => void} */
- let applyFix;
- if (primary === 'always') {
- applyFix = (index) => {
- // eslint-disable-next-line prefer-template
- fixed += value.slice(lastIndex, index) + ' ';
- lastIndex = index;
- };
- } else if (primary === 'never') {
- applyFix = (index) => {
- let whitespaceEndIndex = index + 1;
- while (whitespaceEndIndex < value.length && isWhitespace(value[whitespaceEndIndex])) {
- whitespaceEndIndex++;
- }
- fixed += value.slice(lastIndex, index);
- lastIndex = whitespaceEndIndex;
- };
- } else {
- throw new Error(`Unexpected option: "${primary}"`);
- }
- return {
- applyFix,
- get hasFixed() {
- return Boolean(lastIndex);
- },
- get fixed() {
- return fixed + value.slice(lastIndex);
- },
- };
- }
- root.walkAtRules(/^import$/i, (atRule) => {
- const param = (atRule.raws.params && atRule.raws.params.raw) || atRule.params;
- const fixer = context.fix && createFixer(param);
- check(atRule, param, atRuleParamIndex(atRule), fixer ? fixer.applyFix : undefined);
- if (fixer && fixer.hasFixed) {
- if (atRule.raws.params) {
- atRule.raws.params.raw = fixer.fixed;
- } else {
- atRule.params = fixer.fixed;
- }
- }
- });
- root.walkDecls((decl) => {
- const value = getDeclarationValue(decl);
- const fixer = context.fix && createFixer(value);
- check(decl, value, declarationValueIndex(decl), fixer ? fixer.applyFix : undefined);
- if (fixer && fixer.hasFixed) {
- setDeclarationValue(decl, fixer.fixed);
- }
- });
- };
- };
- rule.ruleName = ruleName;
- rule.messages = messages;
- rule.meta = meta;
- module.exports = rule;
|