1 | import FEATURES from '../data/features.js';
|
2 | import { performFeatureCheck } from '../utils/util.js';
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | const PLUGIN_OPTION_COMMENT = 'doiuse-';
|
18 | const DISABLE_FEATURE_COMMENT = `${PLUGIN_OPTION_COMMENT}disable`;
|
19 | const ENABLE_FEATURE_COMMENT = `${PLUGIN_OPTION_COMMENT}enable`;
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 | function stripUrls(input) {
|
28 | return input.replaceAll(/url\([^)]*\)/g, 'url()');
|
29 | }
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 | export default class Detector {
|
50 | |
51 |
|
52 |
|
53 | constructor(featureList) {
|
54 |
|
55 | this.features = {};
|
56 | for (const feature of featureList) {
|
57 | if (FEATURES[feature]) {
|
58 | this.features[feature] = FEATURES[feature];
|
59 | }
|
60 | }
|
61 |
|
62 | this.ignore = [];
|
63 | }
|
64 |
|
65 | |
66 |
|
67 |
|
68 |
|
69 | comment(comment) {
|
70 | const text = comment.text.toLowerCase();
|
71 |
|
72 | if (!text.startsWith(PLUGIN_OPTION_COMMENT)) return;
|
73 | const option = text.split(' ', 1)[0];
|
74 | const value = text.replace(option, '').trim();
|
75 |
|
76 | switch (option) {
|
77 | case DISABLE_FEATURE_COMMENT: {
|
78 | if (value === '') {
|
79 |
|
80 | this.ignore = Object.keys(this.features);
|
81 | } else {
|
82 | for (const feat of value.split(',')) {
|
83 |
|
84 | const f = feat.trim();
|
85 | if (!this.ignore.includes(f)) {
|
86 | this.ignore.push(f);
|
87 | }
|
88 | }
|
89 | }
|
90 | break;
|
91 | }
|
92 | case ENABLE_FEATURE_COMMENT: {
|
93 | if (value === '') {
|
94 | this.ignore = [];
|
95 | } else {
|
96 | const without = new Set(value.split(',').map((feat) => feat.trim()));
|
97 | this.ignore = this.ignore.filter((index) => !without.has(index));
|
98 | }
|
99 | break;
|
100 | }
|
101 | default:
|
102 | }
|
103 | }
|
104 |
|
105 | |
106 |
|
107 |
|
108 |
|
109 |
|
110 | node(node, callback) {
|
111 | node.each((child) => {
|
112 | if (child.type === 'comment') {
|
113 | this.comment(child);
|
114 | return;
|
115 | }
|
116 |
|
117 | for (const [feat] of Object.entries(this.features).filter(([, featValue]) => {
|
118 | if (!featValue) return false;
|
119 | if (typeof featValue === 'function') {
|
120 | return featValue(child);
|
121 | }
|
122 | if (Array.isArray(featValue)) {
|
123 | return featValue.some((function_) => function_(child));
|
124 | }
|
125 | if (child.type !== 'decl') {
|
126 | return false;
|
127 | }
|
128 |
|
129 | return Object.entries(featValue).some(([property, value]) => {
|
130 | if (property !== '' && property !== child.prop) return false;
|
131 | if (value === true) return true;
|
132 | if (value === false) return false;
|
133 | return performFeatureCheck(value, stripUrls(child.value));
|
134 | });
|
135 | })) {
|
136 | const feature = (feat);
|
137 | callback({ usage: child, feature, ignore: this.ignore });
|
138 | }
|
139 | if (child.type !== 'decl') {
|
140 | this.node(child, callback);
|
141 | }
|
142 | });
|
143 | }
|
144 |
|
145 | |
146 |
|
147 |
|
148 |
|
149 |
|
150 | process(node, callback) {
|
151 |
|
152 | this.ignore = [];
|
153 |
|
154 |
|
155 | this.node(node, callback);
|
156 | }
|
157 | }
|