Skip to content

Commit 293fcf4

Browse files
authoredJul 2, 2024··
feat: disallow import chunkname w/ webpack eager mode (#100)
1 parent c0cea7b commit 293fcf4

6 files changed

+209
-26
lines changed
 

‎.changeset/grumpy-games-shake.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
feat: webpack comment regex support `webpackFetchPriority`

‎.changeset/sixty-avocados-stare.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"eslint-plugin-import-x": patch
3+
---
4+
5+
Allow empty chunk name when webpackMode: 'eager' is set; add suggestions to remove name in eager mode

‎README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
7979
| Name                            | Description | 💼 | ⚠️ | 🚫 | 🔧 | 💡 ||
8080
| :------------------------------------------------------------------------------- | :-------------------------------------------------------------------------- | :-- | :---- | :-- | :-- | :-- | :-- |
8181
| [consistent-type-specifier-style](docs/rules/consistent-type-specifier-style.md) | Enforce or ban the use of inline type-only markers for named imports. | | | | 🔧 | | |
82-
| [dynamic-import-chunkname](docs/rules/dynamic-import-chunkname.md) | Enforce a leading comment with the webpackChunkName for dynamic imports. | | | | | | |
82+
| [dynamic-import-chunkname](docs/rules/dynamic-import-chunkname.md) | Enforce a leading comment with the webpackChunkName for dynamic imports. | | | | | 💡 | |
8383
| [exports-last](docs/rules/exports-last.md) | Ensure all exports appear after other statements. | | | | | | |
8484
| [extensions](docs/rules/extensions.md) | Ensure consistent use of file extension within the import path. | | | | | | |
8585
| [first](docs/rules/first.md) | Ensure all imports appear before other statements. | | | | 🔧 | | |

‎docs/rules/dynamic-import-chunkname.md

+9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# import-x/dynamic-import-chunkname
22

3+
💡 This rule is manually fixable by [editor suggestions](https://eslint.org/docs/latest/use/core-concepts#rule-suggestions).
4+
35
<!-- end auto-generated rule header -->
46

57
This rule reports any dynamic imports without a webpackChunkName specified in a leading block comment in the proper format.
@@ -56,6 +58,13 @@ import(
5658
// webpackChunkName: "someModule"
5759
'someModule'
5860
)
61+
62+
// chunk names are disallowed when eager mode is set
63+
import(
64+
/* webpackMode: "eager" */
65+
/* webpackChunkName: "someModule" */
66+
'someModule'
67+
)
5968
```
6069

6170
### valid

‎src/rules/dynamic-import-chunkname.ts

+88-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import vm from 'node:vm'
22

33
import type { TSESTree } from '@typescript-eslint/utils'
4+
import type { RuleFixer } from '@typescript-eslint/utils/dist/ts-eslint'
45

56
import { createRule } from '../utils'
67

@@ -16,6 +17,9 @@ type MessageId =
1617
| 'paddedSpaces'
1718
| 'webpackComment'
1819
| 'chunknameFormat'
20+
| 'webpackEagerModeNoChunkName'
21+
| 'webpackRemoveEagerMode'
22+
| 'webpackRemoveChunkName'
1923

2024
export = 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-
/^( ((webpackChunkName: .+)|((webpackPrefetch|webpackPreload): (true|false|-?\d+))|(webpackIgnore: (true|false))|((webpackInclude|webpackExclude): \/.*\/)|(webpackMode: ["'](lazy|lazy-once|eager|weak)["'])|(webpackExports: (["']\w+["']|\[(["']\w+["'], *)+(["']\w+["']*)]))),?)+ $/
73-
const chunkSubstrFormat = ` webpackChunkName: ["']${webpackChunknameFormat}["'],? `
81+
/^( (((webpackChunkName|webpackFetchPriority): .+)|((webpackPrefetch|webpackPreload): (true|false|-?\d+))|(webpackIgnore: (true|false))|((webpackInclude|webpackExclude): \/.+\/)|(webpackMode: ["'](lazy|lazy-once|eager|weak)["'])|(webpackExports: (["']\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',

‎test/rules/dynamic-import-chunkname.spec.ts

+101-21
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ const parser = parsers.BABEL
3939
const pickyChunkNameFormatError = {
4040
messageId: 'chunknameFormat',
4141
data: {
42-
format: ` webpackChunkName: ["']${pickyCommentFormat}["'],? `,
42+
format: `webpackChunkName: ["']${pickyCommentFormat}["'],?`,
4343
},
4444
} as const
4545

@@ -366,15 +366,6 @@ ruleTester.run('dynamic-import-chunkname', rule, {
366366
options,
367367
parser,
368368
},
369-
{
370-
code: `import(
371-
/* webpackChunkName: "someModule" */
372-
/* webpackMode: "eager" */
373-
'someModule'
374-
)`,
375-
options,
376-
parser,
377-
},
378369
{
379370
code: `import(
380371
/* webpackChunkName: "someModule" */
@@ -426,7 +417,7 @@ ruleTester.run('dynamic-import-chunkname', rule, {
426417
/* webpackPrefetch: true */
427418
/* webpackPreload: true */
428419
/* webpackIgnore: false */
429-
/* webpackMode: "eager" */
420+
/* webpackMode: "lazy" */
430421
/* webpackExports: ["default", "named"] */
431422
'someModule'
432423
)`,
@@ -1220,15 +1211,6 @@ describe('TypeScript', () => {
12201211
options,
12211212
parser: typescriptParser,
12221213
},
1223-
{
1224-
code: `import(
1225-
/* webpackChunkName: "someModule" */
1226-
/* webpackMode: "eager" */
1227-
'someModule'
1228-
)`,
1229-
options,
1230-
parser: typescriptParser,
1231-
},
12321214
{
12331215
code: `import(
12341216
/* webpackChunkName: "someModule" */
@@ -1280,7 +1262,7 @@ describe('TypeScript', () => {
12801262
/* webpackPrefetch: true */
12811263
/* webpackPreload: true */
12821264
/* webpackIgnore: false */
1283-
/* webpackMode: "eager" */
1265+
/* webpackMode: "lazy" */
12841266
/* webpackExports: ["default", "named"] */
12851267
'someModule'
12861268
)`,
@@ -1707,6 +1689,104 @@ describe('TypeScript', () => {
17071689
},
17081690
],
17091691
},
1692+
{
1693+
code: `import(
1694+
/* webpackChunkName: "someModule" */
1695+
/* webpackMode: "eager" */
1696+
'someModule'
1697+
)`,
1698+
options,
1699+
parser,
1700+
output: null,
1701+
errors: [
1702+
{
1703+
messageId: 'webpackEagerModeNoChunkName',
1704+
type: nodeType,
1705+
suggestions: [
1706+
{
1707+
messageId: 'webpackRemoveChunkName',
1708+
output: `import(
1709+
/* webpackMode: "eager" */
1710+
'someModule'
1711+
)`,
1712+
},
1713+
{
1714+
messageId: 'webpackRemoveEagerMode',
1715+
output: `import(
1716+
/* webpackChunkName: "someModule" */
1717+
'someModule'
1718+
)`,
1719+
},
1720+
],
1721+
},
1722+
],
1723+
},
1724+
{
1725+
code: `import(
1726+
/* webpackChunkName: "someModule", webpackPrefetch: true */
1727+
/* webpackMode: "eager" */
1728+
'someModule'
1729+
)`,
1730+
options,
1731+
parser,
1732+
output: null,
1733+
errors: [
1734+
{
1735+
messageId: 'webpackEagerModeNoChunkName',
1736+
type: nodeType,
1737+
suggestions: [
1738+
{
1739+
messageId: 'webpackRemoveChunkName',
1740+
output: `import(
1741+
/* webpackPrefetch: true */
1742+
/* webpackMode: "eager" */
1743+
'someModule'
1744+
)`,
1745+
},
1746+
{
1747+
messageId: 'webpackRemoveEagerMode',
1748+
output: `import(
1749+
/* webpackChunkName: "someModule", webpackPrefetch: true */
1750+
'someModule'
1751+
)`,
1752+
},
1753+
],
1754+
},
1755+
],
1756+
},
1757+
{
1758+
code: `import(
1759+
/* webpackChunkName: "someModule" */
1760+
/* webpackMode: "eager", webpackPrefetch: true */
1761+
'someModule'
1762+
)`,
1763+
options,
1764+
parser,
1765+
output: null,
1766+
errors: [
1767+
{
1768+
messageId: 'webpackEagerModeNoChunkName',
1769+
type: nodeType,
1770+
suggestions: [
1771+
{
1772+
messageId: 'webpackRemoveChunkName',
1773+
output: `import(
1774+
/* webpackMode: "eager", webpackPrefetch: true */
1775+
'someModule'
1776+
)`,
1777+
},
1778+
{
1779+
messageId: 'webpackRemoveEagerMode',
1780+
output: `import(
1781+
/* webpackChunkName: "someModule" */
1782+
/* webpackPrefetch: true */
1783+
'someModule'
1784+
)`,
1785+
},
1786+
],
1787+
},
1788+
],
1789+
},
17101790
],
17111791
})
17121792
})

0 commit comments

Comments
 (0)
Please sign in to comment.