UNPKG

3.62 kBJavaScriptView Raw
1'use strict';
2
3const _ = require('lodash');
4const atRuleParamIndex = require('../../utils/atRuleParamIndex');
5const declarationValueIndex = require('../../utils/declarationValueIndex');
6const getUnitFromValueNode = require('../../utils/getUnitFromValueNode');
7const mediaParser = require('postcss-media-query-parser').default;
8const optionsMatches = require('../../utils/optionsMatches');
9const report = require('../../utils/report');
10const ruleMessages = require('../../utils/ruleMessages');
11const validateObjectWithArrayProps = require('../../utils/validateObjectWithArrayProps');
12const validateOptions = require('../../utils/validateOptions');
13const valueParser = require('postcss-value-parser');
14
15const ruleName = 'unit-blacklist';
16
17const messages = ruleMessages(ruleName, {
18 rejected: (unit) => `Unexpected unit "${unit}"`,
19});
20
21// a function to retrieve only the media feature name
22// could be externalized in an utils function if needed in other code
23const getMediaFeatureName = (mediaFeatureNode) => {
24 const value = mediaFeatureNode.value.toLowerCase();
25
26 return /((-?\w*)*)/i.exec(value)[1];
27};
28
29function rule(blacklistInput, options) {
30 const blacklist = [].concat(blacklistInput);
31
32 return (root, result) => {
33 const validOptions = validateOptions(
34 result,
35 ruleName,
36 {
37 actual: blacklist,
38 possible: [_.isString],
39 },
40 {
41 optional: true,
42 actual: options,
43 possible: {
44 ignoreProperties: validateObjectWithArrayProps([_.isString, _.isRegExp]),
45 ignoreMediaFeatureNames: validateObjectWithArrayProps([_.isString, _.isRegExp]),
46 },
47 },
48 );
49
50 if (!validOptions) {
51 return;
52 }
53
54 function check(node, nodeIndex, valueNode, input, option) {
55 const unit = getUnitFromValueNode(valueNode);
56
57 // There is not unit or it is not configured as a violation
58 if (!unit || (unit && !blacklist.includes(unit.toLowerCase()))) {
59 return;
60 }
61
62 // The unit has an ignore option for the specific input
63 if (optionsMatches(option, unit.toLowerCase(), input)) {
64 return;
65 }
66
67 report({
68 index: nodeIndex + valueNode.sourceIndex,
69 message: messages.rejected(unit),
70 node,
71 result,
72 ruleName,
73 });
74 }
75
76 function checkMedia(node, value, getIndex) {
77 mediaParser(node.params).walk(/^media-feature$/i, (mediaFeatureNode) => {
78 const mediaName = getMediaFeatureName(mediaFeatureNode);
79 const parentValue = mediaFeatureNode.parent.value;
80
81 valueParser(value).walk((valueNode) => {
82 // Ignore all non-word valueNode and
83 // the values not included in the parentValue string
84 if (valueNode.type !== 'word' || !parentValue.includes(valueNode.value)) {
85 return;
86 }
87
88 check(
89 node,
90 getIndex(node),
91 valueNode,
92 mediaName,
93 options ? options.ignoreMediaFeatureNames : {},
94 );
95 });
96 });
97 }
98
99 function checkDecl(node, value, getIndex) {
100 // make sure multiplication operations (*) are divided - not handled
101 // by postcss-value-parser
102 value = value.replace(/\*/g, ',');
103
104 valueParser(value).walk((valueNode) => {
105 // Ignore wrong units within `url` function
106 if (valueNode.type === 'function' && valueNode.value.toLowerCase() === 'url') {
107 return false;
108 }
109
110 check(node, getIndex(node), valueNode, node.prop, options ? options.ignoreProperties : {});
111 });
112 }
113
114 root.walkAtRules(/^media$/i, (atRule) => checkMedia(atRule, atRule.params, atRuleParamIndex));
115 root.walkDecls((decl) => checkDecl(decl, decl.value, declarationValueIndex));
116 };
117}
118
119rule.primaryOptionArray = true;
120
121rule.ruleName = ruleName;
122rule.messages = messages;
123module.exports = rule;