11import vm from 'node:vm'
22
33import type { TSESTree } from '@typescript-eslint/utils'
4+ import type { RuleFixer } from '@typescript-eslint/utils/dist/ts-eslint'
45
56import { createRule } from '../utils'
67
@@ -16,6 +17,9 @@ type MessageId =
1617 | 'paddedSpaces'
1718 | 'webpackComment'
1819 | 'chunknameFormat'
20+ | 'webpackEagerModeNoChunkName'
21+ | 'webpackRemoveEagerMode'
22+ | 'webpackRemoveChunkName'
1923
2024export = createRule < [ Options ?] , MessageId > ( {
2125 name : 'dynamic-import-chunkname' ,
@@ -26,6 +30,7 @@ export = createRule<[Options?], MessageId>({
2630 description :
2731 'Enforce a leading comment with the webpackChunkName for dynamic imports.' ,
2832 } ,
33+ hasSuggestions : true ,
2934 schema : [
3035 {
3136 type : 'object' ,
@@ -56,7 +61,11 @@ export = createRule<[Options?], MessageId>({
5661 webpackComment :
5762 'dynamic imports require a "webpack" comment with valid syntax' ,
5863 chunknameFormat :
59- 'dynamic imports require a leading comment in the form /*{{format}}*/' ,
64+ 'dynamic imports require a leading comment in the form /* {{format}} */' ,
65+ webpackEagerModeNoChunkName :
66+ 'dynamic imports using eager mode do not need a webpackChunkName' ,
67+ webpackRemoveEagerMode : 'Remove webpackMode' ,
68+ webpackRemoveChunkName : 'Remove webpackChunkName' ,
6069 } ,
6170 } ,
6271 defaultOptions : [ ] ,
@@ -69,9 +78,12 @@ export = createRule<[Options?], MessageId>({
6978
7079 const paddedCommentRegex = / ^ ( \S [ \S \s ] + \S ) $ /
7180 const commentStyleRegex =
72- / ^ ( ( ( w e b p a c k C h u n k N a m e : .+ ) | ( ( w e b p a c k P r e f e t c h | w e b p a c k P r e l o a d ) : ( t r u e | f a l s e | - ? \d + ) ) | ( w e b p a c k I g n o r e : ( t r u e | f a l s e ) ) | ( ( w e b p a c k I n c l u d e | w e b p a c k E x c l u d e ) : \/ .* \/ ) | ( w e b p a c k M o d e : [ " ' ] ( l a z y | l a z y - o n c e | e a g e r | w e a k ) [ " ' ] ) | ( w e b p a c k E x p o r t s : ( [ " ' ] \w + [ " ' ] | \[ ( [ " ' ] \w + [ " ' ] , * ) + ( [ " ' ] \w + [ " ' ] * ) ] ) ) ) , ? ) + $ /
73- const chunkSubstrFormat = ` webpackChunkName: ["']${ webpackChunknameFormat } ["'],? `
81+ / ^ ( ( ( ( w e b p a c k C h u n k N a m e | w e b p a c k F e t c h P r i o r i t y ) : .+ ) | ( ( w e b p a c k P r e f e t c h | w e b p a c k P r e l o a d ) : ( t r u e | f a l s e | - ? \d + ) ) | ( w e b p a c k I g n o r e : ( t r u e | f a l s e ) ) | ( ( w e b p a c k I n c l u d e | w e b p a c k E x c l u d e ) : \/ .+ \/ ) | ( w e b p a c k M o d e : [ " ' ] ( l a z y | l a z y - o n c e | e a g e r | w e a k ) [ " ' ] ) | ( w e b p a c k E x p o r t s : ( [ " ' ] \w + [ " ' ] | \[ ( [ " ' ] \w + [ " ' ] , * ) + ( [ " ' ] \w + [ " ' ] * ) ] ) ) ) , ? ) + $ /
82+
83+ const chunkSubstrFormat = `webpackChunkName: ["']${ webpackChunknameFormat } ["'],?`
7484 const chunkSubstrRegex = new RegExp ( chunkSubstrFormat )
85+ const eagerModeFormat = `webpackMode: ["']eager["'],?`
86+ const eagerModeRegex = new RegExp ( eagerModeFormat )
7587
7688 function run ( node : TSESTree . Node , arg : TSESTree . Node ) {
7789 const { sourceCode } = context
@@ -86,6 +98,7 @@ export = createRule<[Options?], MessageId>({
8698 }
8799
88100 let isChunknamePresent = false
101+ let isEagerModePresent = false
89102
90103 for ( const comment of leadingComments ) {
91104 if ( comment . type !== 'Block' ) {
@@ -123,12 +136,83 @@ export = createRule<[Options?], MessageId>({
123136 return
124137 }
125138
139+ if ( eagerModeRegex . test ( comment . value ) ) {
140+ isEagerModePresent = true
141+ }
142+
126143 if ( chunkSubstrRegex . test ( comment . value ) ) {
127144 isChunknamePresent = true
128145 }
129146 }
130147
131- if ( ! isChunknamePresent && ! allowEmpty ) {
148+ const removeCommentsAndLeadingSpaces = (
149+ fixer : RuleFixer ,
150+ comment : TSESTree . Comment ,
151+ ) => {
152+ const leftToken = sourceCode . getTokenBefore ( comment )
153+ const leftComments = sourceCode . getCommentsBefore ( comment )
154+ if ( leftToken ) {
155+ if ( leftComments . length > 0 ) {
156+ return fixer . removeRange ( [
157+ Math . max (
158+ leftToken . range [ 1 ] ,
159+ leftComments [ leftComments . length - 1 ] . range [ 1 ] ,
160+ ) ,
161+ comment . range [ 1 ] ,
162+ ] )
163+ }
164+ return fixer . removeRange ( [ leftToken . range [ 1 ] , comment . range [ 1 ] ] )
165+ }
166+ return fixer . remove ( comment )
167+ }
168+
169+ if ( isChunknamePresent && isEagerModePresent ) {
170+ context . report ( {
171+ node,
172+ messageId : 'webpackEagerModeNoChunkName' ,
173+ suggest : [
174+ {
175+ messageId : 'webpackRemoveChunkName' ,
176+ fix ( fixer ) {
177+ for ( const comment of leadingComments ) {
178+ if ( chunkSubstrRegex . test ( comment . value ) ) {
179+ const replacement = comment . value
180+ . replace ( chunkSubstrRegex , '' )
181+ . trim ( )
182+ . replace ( / , $ / , '' )
183+
184+ return replacement === ''
185+ ? removeCommentsAndLeadingSpaces ( fixer , comment )
186+ : fixer . replaceText ( comment , `/* ${ replacement } */` )
187+ }
188+ }
189+
190+ return null
191+ } ,
192+ } ,
193+ {
194+ messageId : 'webpackRemoveEagerMode' ,
195+ fix ( fixer ) {
196+ for ( const comment of leadingComments ) {
197+ if ( eagerModeRegex . test ( comment . value ) ) {
198+ const replacement = comment . value
199+ . replace ( eagerModeRegex , '' )
200+ . trim ( )
201+ . replace ( / , $ / , '' )
202+ return replacement === ''
203+ ? removeCommentsAndLeadingSpaces ( fixer , comment )
204+ : fixer . replaceText ( comment , `/* ${ replacement } */` )
205+ }
206+ }
207+
208+ return null
209+ } ,
210+ } ,
211+ ] ,
212+ } )
213+ }
214+
215+ if ( ! isChunknamePresent && ! allowEmpty && ! isEagerModePresent ) {
132216 context . report ( {
133217 node,
134218 messageId : 'chunknameFormat' ,
0 commit comments