UNPKG

4.73 kBJavaScriptView Raw
1'use strict';
2
3const declarationValueIndex = require('../../utils/declarationValueIndex');
4const getDeclarationValue = require('../../utils/getDeclarationValue');
5const isSingleLineString = require('../../utils/isSingleLineString');
6const isStandardSyntaxFunction = require('../../utils/isStandardSyntaxFunction');
7const report = require('../../utils/report');
8const ruleMessages = require('../../utils/ruleMessages');
9const setDeclarationValue = require('../../utils/setDeclarationValue');
10const validateOptions = require('../../utils/validateOptions');
11const valueParser = require('postcss-value-parser');
12
13const ruleName = 'function-parentheses-space-inside';
14
15const messages = ruleMessages(ruleName, {
16 expectedOpening: 'Expected single space after "("',
17 rejectedOpening: 'Unexpected whitespace after "("',
18 expectedClosing: 'Expected single space before ")"',
19 rejectedClosing: 'Unexpected whitespace before ")"',
20 expectedOpeningSingleLine: 'Expected single space after "(" in a single-line function',
21 rejectedOpeningSingleLine: 'Unexpected whitespace after "(" in a single-line function',
22 expectedClosingSingleLine: 'Expected single space before ")" in a single-line function',
23 rejectedClosingSingleLine: 'Unexpected whitespace before ")" in a single-line function',
24});
25
26/** @type {import('stylelint').Rule} */
27const rule = (primary, _secondaryOptions, context) => {
28 return (root, result) => {
29 const validOptions = validateOptions(result, ruleName, {
30 actual: primary,
31 possible: ['always', 'never', 'always-single-line', 'never-single-line'],
32 });
33
34 if (!validOptions) {
35 return;
36 }
37
38 root.walkDecls((decl) => {
39 if (!decl.value.includes('(')) {
40 return;
41 }
42
43 let hasFixed = false;
44 const declValue = getDeclarationValue(decl);
45 const parsedValue = valueParser(declValue);
46
47 parsedValue.walk((valueNode) => {
48 if (valueNode.type !== 'function') {
49 return;
50 }
51
52 if (!isStandardSyntaxFunction(valueNode)) {
53 return;
54 }
55
56 // Ignore function without parameters
57 if (!valueNode.nodes.length) {
58 return;
59 }
60
61 const functionString = valueParser.stringify(valueNode);
62 const isSingleLine = isSingleLineString(functionString);
63
64 // Check opening ...
65
66 const openingIndex = valueNode.sourceIndex + valueNode.value.length + 1;
67
68 if (primary === 'always' && valueNode.before !== ' ') {
69 if (context.fix) {
70 hasFixed = true;
71 valueNode.before = ' ';
72 } else {
73 complain(messages.expectedOpening, openingIndex);
74 }
75 }
76
77 if (primary === 'never' && valueNode.before !== '') {
78 if (context.fix) {
79 hasFixed = true;
80 valueNode.before = '';
81 } else {
82 complain(messages.rejectedOpening, openingIndex);
83 }
84 }
85
86 if (isSingleLine && primary === 'always-single-line' && valueNode.before !== ' ') {
87 if (context.fix) {
88 hasFixed = true;
89 valueNode.before = ' ';
90 } else {
91 complain(messages.expectedOpeningSingleLine, openingIndex);
92 }
93 }
94
95 if (isSingleLine && primary === 'never-single-line' && valueNode.before !== '') {
96 if (context.fix) {
97 hasFixed = true;
98 valueNode.before = '';
99 } else {
100 complain(messages.rejectedOpeningSingleLine, openingIndex);
101 }
102 }
103
104 // Check closing ...
105
106 const closingIndex = valueNode.sourceIndex + functionString.length - 2;
107
108 if (primary === 'always' && valueNode.after !== ' ') {
109 if (context.fix) {
110 hasFixed = true;
111 valueNode.after = ' ';
112 } else {
113 complain(messages.expectedClosing, closingIndex);
114 }
115 }
116
117 if (primary === 'never' && valueNode.after !== '') {
118 if (context.fix) {
119 hasFixed = true;
120 valueNode.after = '';
121 } else {
122 complain(messages.rejectedClosing, closingIndex);
123 }
124 }
125
126 if (isSingleLine && primary === 'always-single-line' && valueNode.after !== ' ') {
127 if (context.fix) {
128 hasFixed = true;
129 valueNode.after = ' ';
130 } else {
131 complain(messages.expectedClosingSingleLine, closingIndex);
132 }
133 }
134
135 if (isSingleLine && primary === 'never-single-line' && valueNode.after !== '') {
136 if (context.fix) {
137 hasFixed = true;
138 valueNode.after = '';
139 } else {
140 complain(messages.rejectedClosingSingleLine, closingIndex);
141 }
142 }
143 });
144
145 if (hasFixed) {
146 setDeclarationValue(decl, parsedValue.toString());
147 }
148
149 /**
150 * @param {string} message
151 * @param {number} offset
152 */
153 function complain(message, offset) {
154 report({
155 ruleName,
156 result,
157 message,
158 node: decl,
159 index: declarationValueIndex(decl) + offset,
160 });
161 }
162 });
163 };
164};
165
166rule.ruleName = ruleName;
167rule.messages = messages;
168module.exports = rule;