UNPKG

4.43 kBJavaScriptView Raw
1const features = require('../data/features');
2
3const PLUGIN_OPTION_COMMENT = 'doiuse-';
4const DISABLE_FEATURE_COMMENT = PLUGIN_OPTION_COMMENT + 'disable';
5const ENABLE_FEATURE_COMMENT = PLUGIN_OPTION_COMMENT + 'enable';
6/*
7 * str: string to search in.
8 * searchfor: string or pattern to search for.
9 */
10
11function isFoundIn(str) {
12 str = stripUrls(str);
13 return function find(searchfor) {
14 if (searchfor instanceof RegExp) return searchfor.test(str);else if (typeof searchfor === 'function') return searchfor(str);else return str && str.indexOf(searchfor) >= 0;
15 };
16}
17/*
18 * Strip the contents of url literals so they aren't matched
19 * by our naive substring matching.
20 */
21
22
23function stripUrls(str) {
24 return str.replace(/url\([^\)]*\)/g, 'url()'); // eslint-disable-line no-useless-escape
25}
26/**
27 * Detect the use of any of a given list of CSS features.
28 * ```
29 * var detector = new Detector(featureList)
30 * detector.process(css, cb)
31 * ```
32 *
33 * `featureList`: an array of feature slugs (see caniuse-db)
34 * `cb`: a callback that gets called for each usage of one of the given features,
35 * called with an argument like:
36 * ```
37 * {
38 * usage: {} // postcss node where usage was found
39 * feature: {} // caniuse-db feature slug
40 * ignore: {} // caniuse-db feature to ignore in current file
41 * }
42 * ```
43 */
44
45
46class Detector {
47 constructor(featureList) {
48 this.features = featureList.reduce((result, feature) => {
49 if (features[feature]) {
50 result[feature] = features[feature];
51 }
52
53 return result;
54 }, {});
55 this.ignore = [];
56 }
57
58 decl(decl, cb) {
59 for (let feat in this.features) {
60 const properties = this.features[feat].properties || [];
61 const values = this.features[feat].values;
62
63 if (properties.filter(isFoundIn(decl.prop)).length > 0) {
64 if (!values || values.filter(isFoundIn(decl.value)).length > 0) {
65 const result = {
66 usage: decl,
67 feature: feat,
68 ignore: this.ignore
69 };
70 cb(result);
71 }
72 }
73 }
74 }
75
76 rule(rule, cb) {
77 for (let feat in this.features) {
78 const selectors = this.features[feat].selectors || [];
79
80 if (selectors.filter(isFoundIn(rule.selector)).length > 0) {
81 const result = {
82 usage: rule,
83 feature: feat,
84 ignore: this.ignore
85 };
86 cb(result);
87 }
88 }
89
90 this.node(rule, cb);
91 }
92
93 atrule(atrule, cb) {
94 for (let feat in this.features) {
95 const atrules = this.features[feat].atrules || [];
96 const params = this.features[feat].params;
97
98 if (atrules.filter(isFoundIn(atrule.name)).length > 0) {
99 if (!params || params.filter(isFoundIn(atrule.params)).length > 0) {
100 const result = {
101 usage: atrule,
102 feature: feat,
103 ignore: this.ignore
104 };
105 cb(result);
106 }
107 }
108 }
109
110 this.node(atrule, cb);
111 }
112
113 comment(comment, cb) {
114 const text = comment.text.toLowerCase();
115
116 if (text.startsWith(PLUGIN_OPTION_COMMENT)) {
117 const option = text.split(' ', 1)[0];
118 const value = text.replace(option, '').trim();
119
120 switch (option) {
121 case DISABLE_FEATURE_COMMENT:
122 if (value === '') {
123 this.ignore = Object.keys(this.features);
124 } else {
125 value.split(',').map(feat => feat.trim()).forEach(feat => {
126 if (this.ignore.indexOf(feat) < 0) {
127 this.ignore.push(feat);
128 }
129 });
130 }
131
132 break;
133
134 case ENABLE_FEATURE_COMMENT:
135 if (value === '') {
136 this.ignore = [];
137 } else {
138 const without = value.split(',').map(feat => feat.trim());
139 this.ignore = this.ignore.filter(i => without.indexOf(i) < 0);
140 }
141
142 break;
143 }
144 }
145 }
146
147 node(node, cb) {
148 node.each(child => {
149 switch (child.type) {
150 case 'rule':
151 this.rule(child, cb);
152 break;
153
154 case 'decl':
155 this.decl(child, cb);
156 break;
157
158 case 'atrule':
159 this.atrule(child, cb);
160 break;
161
162 case 'comment':
163 this.comment(child, cb);
164 }
165 });
166 }
167
168 process(node, cb) {
169 // Reset ignoring rules specified by inline comments per each file
170 this.ignore = []; // Recursively walk nodes in file
171
172 this.node(node, cb);
173 }
174
175}
176
177module.exports = Detector;
\No newline at end of file