UNPKG

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