@@ -14,18 +14,56 @@ const messages = {
1414 'All polyfilled features imported from `{{coreJsModule}}` are available as built-ins. Use the built-ins instead.' ,
1515} ;
1616
17- const additionalPolyfillPatterns = {
18- 'es.promise.finally' : '|( p-finally)' ,
19- 'es.object.set-prototype-of' : '|( setprototypeof)' ,
20- 'es.string.code-point-at' : '|( code-point-at)' ,
17+ const additionalPolyfillModules = {
18+ 'es.promise.finally' : [ ' p-finally' ] ,
19+ 'es.object.set-prototype-of' : [ ' setprototypeof' ] ,
20+ 'es.string.code-point-at' : [ ' code-point-at' ] ,
2121} ;
22+ const additionalPolyfillPatterns = Object . fromEntries (
23+ Object . entries ( additionalPolyfillModules ) . map ( ( [ feature , modules ] ) => [ feature , `|(${ modules . join ( '|' ) } )` ] ) ,
24+ ) ;
2225
2326const prefixes = '(mdn-polyfills/|polyfill-)' ;
2427const suffixes = '(-polyfill)' ;
2528const delimiter = String . raw `(\.|-|\.prototype\.|/)?` ;
29+ const moduleDelimiter = / [ . / - ] / u;
30+
31+ const getFirstSegment = value => {
32+ const [ firstSegment = '' ] = value . split ( moduleDelimiter ) ;
33+ return firstSegment ;
34+ } ;
35+
36+ const stripPolyfillPrefix = value => {
37+ if ( value . startsWith ( 'polyfill-' ) ) {
38+ return value . slice ( 'polyfill-' . length ) ;
39+ }
40+
41+ if ( value . startsWith ( 'mdn-polyfills/' ) ) {
42+ return value . slice ( 'mdn-polyfills/' . length ) ;
43+ }
44+
45+ return value ;
46+ } ;
47+
48+ function addPolyfillToken ( tokens , value ) {
49+ if ( ! value ) {
50+ return ;
51+ }
52+
53+ const lowercaseValue = value . toLowerCase ( ) ;
54+ tokens . add ( lowercaseValue ) ;
55+ tokens . add ( getFirstSegment ( lowercaseValue ) ) ;
56+
57+ const camelCasedValue = camelCase ( value ) . toLowerCase ( ) ;
58+ tokens . add ( camelCasedValue ) ;
59+ tokens . add ( getFirstSegment ( camelCasedValue ) ) ;
60+ }
2661
2762const polyfills = Object . keys ( compatData ) . map ( feature => {
28- let [ ecmaVersion , constructorName , methodName = '' ] = feature . split ( '.' ) ;
63+ const [ rawEcmaVersion , rawConstructorName , rawMethodName = '' ] = feature . split ( '.' ) ;
64+ let ecmaVersion = rawEcmaVersion ;
65+ let constructorName = rawConstructorName ;
66+ let methodName = rawMethodName ;
2967
3068 if ( ecmaVersion === 'es' ) {
3169 ecmaVersion = String . raw `(es\d*)` ;
@@ -49,8 +87,145 @@ const polyfills = Object.keys(compatData).map(feature => {
4987 return {
5088 feature,
5189 pattern : new RegExp ( patterns . join ( '' ) , 'i' ) ,
90+ tokens : ( ( ) => {
91+ const tokens = new Set ( ) ;
92+
93+ if ( rawEcmaVersion === 'es' ) {
94+ tokens . add ( 'es' ) ;
95+ } else {
96+ addPolyfillToken ( tokens , rawEcmaVersion ) ;
97+ }
98+
99+ addPolyfillToken ( tokens , rawConstructorName ) ;
100+ addPolyfillToken ( tokens , rawMethodName ) ;
101+
102+ for ( const module of additionalPolyfillModules [ feature ] || [ ] ) {
103+ addPolyfillToken ( tokens , module ) ;
104+ }
105+
106+ return tokens ;
107+ } ) ( ) ,
52108 } ;
53109} ) ;
110+ const polyfillsByToken = new Map ( ) ;
111+ const polyfillTokensByFirstCharacter = new Map ( ) ;
112+ const esConstructorTokens = new Set ( ) ;
113+
114+ for ( const polyfill of polyfills ) {
115+ const [ ecmaVersion , constructorName ] = polyfill . feature . split ( '.' ) ;
116+ if ( ecmaVersion === 'es' ) {
117+ esConstructorTokens . add ( constructorName . toLowerCase ( ) ) ;
118+ esConstructorTokens . add ( camelCase ( constructorName ) . toLowerCase ( ) ) ;
119+ }
120+
121+ for ( const token of polyfill . tokens ) {
122+ if ( ! token ) {
123+ continue ;
124+ }
125+
126+ if ( polyfillsByToken . has ( token ) ) {
127+ polyfillsByToken . get ( token ) . push ( polyfill ) ;
128+ } else {
129+ polyfillsByToken . set ( token , [ polyfill ] ) ;
130+ }
131+
132+ const firstCharacter = token [ 0 ] ;
133+ if ( polyfillTokensByFirstCharacter . has ( firstCharacter ) ) {
134+ polyfillTokensByFirstCharacter . get ( firstCharacter ) . add ( token ) ;
135+ } else {
136+ polyfillTokensByFirstCharacter . set ( firstCharacter , new Set ( [ token ] ) ) ;
137+ }
138+ }
139+ }
140+
141+ const hasEsConstructorPrefix = value => {
142+ for ( const token of esConstructorTokens ) {
143+ if ( value . startsWith ( token ) ) {
144+ return true ;
145+ }
146+ }
147+
148+ return false ;
149+ } ;
150+
151+ const isPotentialEsPrefix = importedModule => {
152+ if ( ! importedModule . startsWith ( 'es' ) ) {
153+ return false ;
154+ }
155+
156+ let constructorIndex = 2 ;
157+ while (
158+ constructorIndex < importedModule . length
159+ && importedModule [ constructorIndex ] >= '0'
160+ && importedModule [ constructorIndex ] <= '9'
161+ ) {
162+ constructorIndex ++ ;
163+ }
164+
165+ if ( importedModule . startsWith ( '.prototype.' , constructorIndex ) ) {
166+ constructorIndex += '.prototype.' . length ;
167+ } else if ( [ '.' , '-' , '/' ] . includes ( importedModule [ constructorIndex ] ) ) {
168+ constructorIndex ++ ;
169+ }
170+
171+ return hasEsConstructorPrefix ( importedModule . slice ( constructorIndex ) ) ;
172+ } ;
173+
174+ const getPolyfillCandidates = importedModule => {
175+ const normalizedImportedModule = stripPolyfillPrefix ( importedModule ) ;
176+ if ( ! normalizedImportedModule ) {
177+ return ;
178+ }
179+
180+ const firstCharacter = normalizedImportedModule [ 0 ] ;
181+ const tokens = polyfillTokensByFirstCharacter . get ( firstCharacter ) ;
182+ if ( ! tokens ) {
183+ return ;
184+ }
185+
186+ const candidates = new Set ( ) ;
187+ const firstSegment = getFirstSegment ( normalizedImportedModule ) ;
188+ if ( firstSegment === normalizedImportedModule ) {
189+ for ( const token of tokens ) {
190+ if ( token === 'es' ) {
191+ if ( ! isPotentialEsPrefix ( normalizedImportedModule ) ) {
192+ continue ;
193+ }
194+ } else if ( ! normalizedImportedModule . startsWith ( token ) ) {
195+ continue ;
196+ }
197+
198+ for ( const polyfill of polyfillsByToken . get ( token ) ) {
199+ candidates . add ( polyfill ) ;
200+ }
201+ }
202+ } else {
203+ for ( const token of tokens ) {
204+ if (
205+ token === 'es'
206+ || ! firstSegment . startsWith ( token )
207+ ) {
208+ continue ;
209+ }
210+
211+ for ( const polyfill of polyfillsByToken . get ( token ) ) {
212+ candidates . add ( polyfill ) ;
213+ }
214+ }
215+ }
216+
217+ if ( isPotentialEsPrefix ( normalizedImportedModule ) ) {
218+ for ( const polyfill of polyfillsByToken . get ( 'es' ) || [ ] ) {
219+ candidates . add ( polyfill ) ;
220+ }
221+ }
222+
223+ if ( candidates . size === 0 ) {
224+ return ;
225+ }
226+
227+ return [ ...candidates ] ;
228+ } ;
54229
55230function getTargets ( options , dirname ) {
56231 if ( options ?. targets ) {
@@ -81,11 +256,13 @@ function create(context) {
81256 return ;
82257 }
83258
259+ const unavailableFeatureSet = new Set ( unavailableFeatures ) ;
260+
84261 // When core-js graduates a feature from `esnext` to `es`, the entries list both (e.g. `['es.regexp.escape', 'esnext.regexp.escape']`),
85262 // but `coreJsCompat` only includes the `es` version in its unavailable list, making the `esnext` version appear "available".
86263 // To avoid false positives, treat `esnext.*` features as unavailable when their `es.*` counterpart is already in the list.
87264 const checkFeatures = features => ! features . every ( feature =>
88- unavailableFeatures . includes ( feature )
265+ unavailableFeatureSet . has ( feature )
89266 || ( feature . startsWith ( 'esnext.' ) && features . includes ( feature . replace ( 'esnext.' , 'es.' ) ) ) ,
90267 ) ;
91268
@@ -117,14 +294,19 @@ function create(context) {
117294 } ,
118295 } ;
119296 }
120- } else if ( ! unavailableFeatures . includes ( coreJsModuleFeatures [ 0 ] ) ) {
297+ } else if ( ! unavailableFeatureSet . has ( coreJsModuleFeatures [ 0 ] ) ) {
121298 return { node, messageId : MESSAGE_ID_POLYFILL } ;
122299 }
123300
124301 return ;
125302 }
126303
127- const polyfill = polyfills . find ( ( { pattern} ) => pattern . test ( importedModule ) ) ;
304+ const polyfillCandidates = getPolyfillCandidates ( importedModule . toLowerCase ( ) ) ;
305+ if ( ! polyfillCandidates ) {
306+ return ;
307+ }
308+
309+ const polyfill = polyfillCandidates . find ( ( { pattern} ) => pattern . test ( importedModule ) ) ;
128310 if ( polyfill ) {
129311 const [ , namespace , method = '' ] = polyfill . feature . split ( '.' ) ;
130312 const features = coreJsEntries [ `core-js/full/${ namespace } ${ method && '/' } ${ method } ` ] ;
0 commit comments