1
1
import FEATURES from '../data/features.js' ;
2
2
import { performFeatureCheck , stripUrls } from '../utils/util.js' ;
3
3
4
+ /** @typedef {import('../data/features.js').FeatureKeys } FeatureKeys */
5
+ /** @typedef {import('../data/features.js').RuleCheck } RuleCheck */
6
+
4
7
/**
5
8
* @typedef DetectorCallbackArgument
6
9
* @prop {!import('postcss').ChildNode } usage
7
- * @prop {keyof FEATURES } feature
8
- * @prop {(keyof FEATURES & string)[] } ignore
10
+ * @prop {FeatureKeys } feature
11
+ * @prop {(FeatureKeys & string)[] } ignore
9
12
*/
10
13
11
14
/**
@@ -18,6 +21,35 @@ const PLUGIN_OPTION_COMMENT = 'doiuse-';
18
21
const DISABLE_FEATURE_COMMENT = `${ PLUGIN_OPTION_COMMENT } disable` ;
19
22
const ENABLE_FEATURE_COMMENT = `${ PLUGIN_OPTION_COMMENT } enable` ;
20
23
24
+ /**
25
+ * Normalise a Feature into a RuleCheck function.
26
+ * @param {import('../data/features.js').Feature } feature
27
+ * @return {RuleCheck }
28
+ */
29
+ function normaliseFeature ( feature ) {
30
+ if ( typeof feature === 'function' ) {
31
+ return feature ;
32
+ }
33
+ if ( Array . isArray ( feature ) ) {
34
+ return ( child ) => feature . some ( ( function_ ) => function_ ( child ) ) ;
35
+ }
36
+ if ( typeof feature === 'object' ) {
37
+ const properties = Object . entries ( feature ) ;
38
+ return ( child ) => {
39
+ if ( child . type !== 'decl' ) {
40
+ return false ;
41
+ }
42
+ return properties . some ( ( [ property , value ] ) => {
43
+ if ( property !== '' && property !== child . prop ) return false ;
44
+ if ( value === true ) return true ;
45
+ if ( value === false ) return false ;
46
+ return performFeatureCheck ( value , stripUrls ( child . value ) ) ;
47
+ } ) ;
48
+ } ;
49
+ }
50
+ throw new TypeError ( `Invalid feature definition: ${ feature } ` ) ;
51
+ }
52
+
21
53
/**
22
54
* Detect the use of any of a given list of CSS features.
23
55
* ```
@@ -38,17 +70,17 @@ const ENABLE_FEATURE_COMMENT = `${PLUGIN_OPTION_COMMENT}enable`;
38
70
*/
39
71
export default class Detector {
40
72
/**
41
- * @param {(keyof FEATURES & string)[] } featureList an array of feature slugs (see caniuse-db)
73
+ * @param {(FeatureKeys & string)[] } featureList an array of feature slugs (see caniuse-db)
42
74
*/
43
75
constructor ( featureList ) {
44
- /** @type {Partial<FEATURES> } */
45
- this . features = { } ;
46
- for ( const feature of featureList ) {
47
- if ( FEATURES [ feature ] ) {
48
- this . features [ feature ] = FEATURES [ feature ] ;
49
- }
50
- }
51
- /** @type {(keyof FEATURES & string)[] } */
76
+ /** @type {[FeatureKeys, RuleCheck][] } */
77
+ this . features = featureList
78
+ . filter ( ( featureName ) => FEATURES [ featureName ] != null )
79
+ . map ( ( featureName ) => {
80
+ const feature = FEATURES [ featureName ] ;
81
+ return [ featureName , normaliseFeature ( feature ) ] ;
82
+ } ) ;
83
+ /** @type {(FeatureKeys & string)[] } */
52
84
this . ignore = [ ] ;
53
85
}
54
86
@@ -66,8 +98,7 @@ export default class Detector {
66
98
switch ( option ) {
67
99
case DISABLE_FEATURE_COMMENT : {
68
100
if ( value === '' ) {
69
- // @ts -expect-error Skip cast
70
- this . ignore = Object . keys ( this . features ) ;
101
+ this . ignore = this . features . map ( ( [ featureName ] ) => featureName ) ;
71
102
} else {
72
103
for ( const feat of value . split ( ',' ) ) {
73
104
/** @type {any } */
@@ -104,28 +135,11 @@ export default class Detector {
104
135
return ;
105
136
}
106
137
107
- for ( const [ feat ] of Object . entries ( this . features ) . filter ( ( [ , featValue ] ) => {
108
- if ( ! featValue ) return false ;
109
- if ( typeof featValue === 'function' ) {
110
- return featValue ( child ) ;
111
- }
112
- if ( Array . isArray ( featValue ) ) {
113
- return featValue . some ( ( function_ ) => function_ ( child ) ) ;
114
- }
115
- if ( child . type !== 'decl' ) {
116
- return false ;
117
- }
118
-
119
- return Object . entries ( featValue ) . some ( ( [ property , value ] ) => {
120
- if ( property !== '' && property !== child . prop ) return false ;
121
- if ( value === true ) return true ;
122
- if ( value === false ) return false ;
123
- return performFeatureCheck ( value , stripUrls ( child . value ) ) ;
124
- } ) ;
125
- } ) ) {
126
- const feature = /** @type {keyof FEATURES } */ ( feat ) ;
127
- callback ( { usage : child , feature, ignore : this . ignore } ) ;
138
+ const detectedFeatures = this . features . filter ( ( [ , ruleCheck ] ) => ruleCheck ( child ) ) ;
139
+ for ( const [ featureName ] of detectedFeatures ) {
140
+ callback ( { usage : child , feature : featureName , ignore : this . ignore } ) ;
128
141
}
142
+
129
143
if ( child . type !== 'decl' ) {
130
144
this . node ( child , callback ) ;
131
145
}
0 commit comments