1 | import multimatch from 'multimatch';
|
2 |
|
3 | import BrowserSelection from './BrowserSelection.js';
|
4 | import Detector from './Detector.js';
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | export default class DoIUse {
|
31 | static default = null;
|
32 |
|
33 | |
34 |
|
35 |
|
36 | constructor(optionsOrBrowserQuery) {
|
37 | const options = (typeof optionsOrBrowserQuery === 'string')
|
38 | ? { browsers: optionsOrBrowserQuery }
|
39 | : { ...optionsOrBrowserQuery };
|
40 | this.browserQuery = options.browsers;
|
41 | this.onFeatureUsage = options.onFeatureUsage;
|
42 | this.ignoreOptions = options.ignore;
|
43 | this.ignoreFiles = options.ignoreFiles;
|
44 | this.info = this.info.bind(this);
|
45 | this.postcss = this.postcss.bind(this);
|
46 | }
|
47 |
|
48 | |
49 |
|
50 |
|
51 |
|
52 | info(options = {}) {
|
53 | const { browsers, features } = BrowserSelection.missingSupport(this.browserQuery, options.from);
|
54 |
|
55 | return {
|
56 | browsers,
|
57 | features,
|
58 | };
|
59 | }
|
60 |
|
61 |
|
62 | postcss(css, result) {
|
63 | let from;
|
64 | if (css.source && css.source.input) {
|
65 | from = css.source.input.file;
|
66 | }
|
67 | const { features } = BrowserSelection.missingSupport(this.browserQuery, from);
|
68 |
|
69 | const detector = new Detector(Object.keys(features));
|
70 |
|
71 | return detector.process(css, ({ feature, usage, ignore }) => {
|
72 | if (ignore && ignore.includes(feature)) {
|
73 | return;
|
74 | }
|
75 |
|
76 | if (this.ignoreOptions && this.ignoreOptions.includes(feature)) {
|
77 | return;
|
78 | }
|
79 |
|
80 | if (!usage.source) {
|
81 | throw new Error('No source?');
|
82 | }
|
83 | if (this.ignoreFiles && multimatch(usage.source.input.from, this.ignoreFiles).length > 0) {
|
84 | return;
|
85 | }
|
86 |
|
87 | const data = features[feature];
|
88 | if (!data) {
|
89 | throw new Error('No feature data?');
|
90 | }
|
91 | const messages = [];
|
92 | if (data.missing) {
|
93 | messages.push(`not supported by: ${data.missing}`);
|
94 | }
|
95 | if (data.partial) {
|
96 | messages.push(`only partially supported by: ${data.partial}`);
|
97 | }
|
98 |
|
99 | let message = `${data.title} ${messages.join(' and ')} (${feature})`;
|
100 |
|
101 | result.warn(message, { node: usage, plugin: 'doiuse' });
|
102 |
|
103 | if (this.onFeatureUsage) {
|
104 | if (!usage.source) {
|
105 | throw new Error('No usage source?');
|
106 | }
|
107 | const { start, input } = usage.source;
|
108 | if (!start) {
|
109 | throw new Error('No usage source start?');
|
110 | }
|
111 |
|
112 | const map = css.source && css.source.input.map;
|
113 | const mappedStart = map && map.consumer().originalPositionFor(start);
|
114 |
|
115 | const file = (mappedStart && mappedStart.source) || input.file || input.from;
|
116 | const line = (mappedStart && mappedStart.line) || start.line;
|
117 | const column = (mappedStart && mappedStart.column) || start.column;
|
118 |
|
119 | message = `${file}:${line}:${column}: ${message}`;
|
120 |
|
121 | this.onFeatureUsage({
|
122 | feature,
|
123 | featureData: features[feature],
|
124 | usage,
|
125 | message,
|
126 | });
|
127 | }
|
128 | });
|
129 | }
|
130 | }
|