eslint-plugin-restrict-replace-import 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/docs/rules/restrict-import.md +1 -1
- package/lib/rules/restrict-import.js +146 -18
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -126,7 +126,7 @@ You can also restrict specific named imports from a package while allowing other
|
|
|
126
126
|
{
|
|
127
127
|
"target": "restricted-module",
|
|
128
128
|
"namedImports": ["restrictedImport", "alsoRestricted"],
|
|
129
|
-
"replacement": "replacement-module"
|
|
129
|
+
"replacement": "replacement-module" // Object is not supported yet
|
|
130
130
|
}
|
|
131
131
|
]
|
|
132
132
|
]
|
|
@@ -129,7 +129,7 @@ import { useState } from 'successfully-replaced'
|
|
|
129
129
|
[{
|
|
130
130
|
"target": "restricted-module",
|
|
131
131
|
"namedImports": ["restrictedImport", "alsoRestricted"],
|
|
132
|
-
"replacement": "replacement-module"
|
|
132
|
+
"replacement": "replacement-module" // Object is not supported yet
|
|
133
133
|
}]
|
|
134
134
|
]
|
|
135
135
|
}
|
|
@@ -4,9 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
'use strict'
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {string | {[key: string]: string;}} Replacement
|
|
9
|
+
* @typedef {{replacement: Replacement | null; namedImports: string[] | null;}} ImportRestrictionOptions
|
|
10
|
+
*/
|
|
11
|
+
|
|
7
12
|
const createRestrictedPackagesMap = (options) => {
|
|
8
13
|
/**
|
|
9
|
-
* @type {Map<RegExp,
|
|
14
|
+
* @type {Map<RegExp, ImportRestrictionOptions>}
|
|
10
15
|
*/
|
|
11
16
|
const map = new Map()
|
|
12
17
|
options.forEach((config) => {
|
|
@@ -28,7 +33,7 @@ const createRestrictedPackagesMap = (options) => {
|
|
|
28
33
|
/**
|
|
29
34
|
* @param {string} importSource
|
|
30
35
|
* @param {string[]} namedImports
|
|
31
|
-
* @param {Map<RegExp,
|
|
36
|
+
* @param {Map<RegExp, ImportRestrictionOptions>} restrictedPackages
|
|
32
37
|
*/
|
|
33
38
|
const checkIsRestrictedImport = (importSource, namedImports, restrictedPackages) => {
|
|
34
39
|
for (const [pattern, restrictedPackageOptions] of restrictedPackages) {
|
|
@@ -119,7 +124,9 @@ const createImportText = ({ specifiers, source, quote, semicolon = '' }) => {
|
|
|
119
124
|
* @param {string[]} restrictedNames
|
|
120
125
|
* @param {string} replacement
|
|
121
126
|
* @returns {(fixer: import('eslint').Rule.RuleFixer) => void}
|
|
127
|
+
* @deprecated Function no longer used as each restriction is now handled individually
|
|
122
128
|
*/
|
|
129
|
+
// eslint-disable-next-line no-unused-vars
|
|
123
130
|
const createNamedImportReplacer = (context, node, restrictedNames, replacement) => {
|
|
124
131
|
return (fixer) => {
|
|
125
132
|
if (!replacement) return null
|
|
@@ -254,7 +261,7 @@ const createStringReplacer = (sourceNode, replacement, quote) => {
|
|
|
254
261
|
|
|
255
262
|
/**
|
|
256
263
|
* @param {import('estree').ImportDeclaration['source']} sourceNode
|
|
257
|
-
* @param {
|
|
264
|
+
* @param {Replacement} replacementPatterns
|
|
258
265
|
* @param {'"' | "'"} quote
|
|
259
266
|
* @returns {(fixer: import('eslint').Rule.RuleFixer) => void}
|
|
260
267
|
*/
|
|
@@ -276,7 +283,7 @@ const createPatternReplacer = (sourceNode, replacementPatterns, quote) => {
|
|
|
276
283
|
|
|
277
284
|
/**
|
|
278
285
|
* @param {import('estree').ImportDeclaration} node
|
|
279
|
-
* @param {
|
|
286
|
+
* @param {Replacement} replacement
|
|
280
287
|
* @returns {(fixer: import('eslint').Rule.RuleFixer) => void}
|
|
281
288
|
*/
|
|
282
289
|
const createModuleReplacer = (node, replacement) => {
|
|
@@ -291,6 +298,104 @@ const createModuleReplacer = (node, replacement) => {
|
|
|
291
298
|
return createPatternReplacer(node.source, replacement, quote)
|
|
292
299
|
}
|
|
293
300
|
|
|
301
|
+
/**
|
|
302
|
+
* @param {import('eslint').Rule.RuleContext} context
|
|
303
|
+
* @param {import('estree').ImportDeclaration} node
|
|
304
|
+
* @param {Array<{importName: string, replacement: string}>} importRestrictions
|
|
305
|
+
* @returns {(fixer: import('eslint').Rule.RuleFixer) => any}
|
|
306
|
+
*/
|
|
307
|
+
const createMultiNamedImportReplacer = (context, node, importRestrictions) => {
|
|
308
|
+
return (fixer) => {
|
|
309
|
+
if (!importRestrictions.length) return null
|
|
310
|
+
|
|
311
|
+
const quote = getQuoteStyle(node.source.raw)
|
|
312
|
+
const semicolon = node.source.raw.endsWith(';') || node.source.value.endsWith(';') ? ';' : ''
|
|
313
|
+
const fixes = []
|
|
314
|
+
|
|
315
|
+
const allRestrictedNames = importRestrictions.map((r) => r.importName)
|
|
316
|
+
|
|
317
|
+
// Group imports by replacement
|
|
318
|
+
const groupedByReplacement = importRestrictions.reduce((acc, restriction) => {
|
|
319
|
+
if (!restriction.replacement) return acc
|
|
320
|
+
|
|
321
|
+
if (!acc[restriction.replacement]) {
|
|
322
|
+
acc[restriction.replacement] = []
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
acc[restriction.replacement].push(restriction.importName)
|
|
326
|
+
|
|
327
|
+
return acc
|
|
328
|
+
}, {})
|
|
329
|
+
|
|
330
|
+
// Find non-restricted specifiers from the original import
|
|
331
|
+
const remainingSpecifiers = node.specifiers.filter(
|
|
332
|
+
(specifier) => specifier.type !== 'ImportSpecifier' || !allRestrictedNames.includes(specifier.imported.name),
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
// Update or remove the original import
|
|
336
|
+
if (remainingSpecifiers.length === 0) {
|
|
337
|
+
fixes.push(fixer.remove(node))
|
|
338
|
+
} else {
|
|
339
|
+
const newImportText = createImportText({
|
|
340
|
+
specifiers: remainingSpecifiers,
|
|
341
|
+
source: node.source.value,
|
|
342
|
+
quote,
|
|
343
|
+
semicolon,
|
|
344
|
+
})
|
|
345
|
+
fixes.push(fixer.replaceText(node, newImportText))
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Create new imports for each replacement module
|
|
349
|
+
const { sourceCode } = context
|
|
350
|
+
const allImports = sourceCode.ast.body.filter(
|
|
351
|
+
(node) => node.type === 'ImportDeclaration' && node.source.type === 'Literal',
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
// Process each replacement module
|
|
355
|
+
Object.entries(groupedByReplacement).forEach(([replacement, restrictedNames]) => {
|
|
356
|
+
// Find specifiers to move
|
|
357
|
+
const specifiersToMove = restrictedNames
|
|
358
|
+
.map((name) => {
|
|
359
|
+
const specifier = node.specifiers.find((s) => s.type === 'ImportSpecifier' && s.imported.name === name)
|
|
360
|
+
return specifier
|
|
361
|
+
? {
|
|
362
|
+
imported: specifier.imported.name,
|
|
363
|
+
local: specifier.local.name,
|
|
364
|
+
}
|
|
365
|
+
: null
|
|
366
|
+
})
|
|
367
|
+
.filter(Boolean)
|
|
368
|
+
|
|
369
|
+
if (specifiersToMove.length === 0) return
|
|
370
|
+
|
|
371
|
+
// Find existing import for the same replacement module
|
|
372
|
+
const existingReplacementImport = allImports.find((importNode) => importNode.source.value === replacement)
|
|
373
|
+
|
|
374
|
+
if (existingReplacementImport) {
|
|
375
|
+
// Add to existing import
|
|
376
|
+
fixes.push(
|
|
377
|
+
...updateExistingImport(
|
|
378
|
+
fixer,
|
|
379
|
+
sourceCode,
|
|
380
|
+
existingReplacementImport,
|
|
381
|
+
specifiersToMove,
|
|
382
|
+
quote,
|
|
383
|
+
semicolon,
|
|
384
|
+
replacement,
|
|
385
|
+
),
|
|
386
|
+
)
|
|
387
|
+
} else {
|
|
388
|
+
// Create new import
|
|
389
|
+
const newSpecifiersText = formatSpecifiers(specifiersToMove)
|
|
390
|
+
const newImport = `import { ${newSpecifiersText} } from ${quote}${replacement}${quote}${semicolon}`
|
|
391
|
+
fixes.push(fixer.insertTextBefore(node, newImport + '\n'))
|
|
392
|
+
}
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
return fixes
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
294
399
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
295
400
|
module.exports = {
|
|
296
401
|
meta: {
|
|
@@ -392,26 +497,49 @@ module.exports = {
|
|
|
392
497
|
return
|
|
393
498
|
}
|
|
394
499
|
|
|
395
|
-
|
|
500
|
+
// Find potential rules and replacement mappings for multiple restricted named imports
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* @type {{importName: string, replacement: string | null, pattern: RegExp}[]}
|
|
504
|
+
*/
|
|
505
|
+
const importRestrictions = []
|
|
506
|
+
|
|
507
|
+
// Check each named import for restrictions
|
|
508
|
+
namedImports.forEach((importName) => {
|
|
509
|
+
for (const [pattern, options] of restrictedPackages.entries()) {
|
|
510
|
+
if (
|
|
511
|
+
pattern.test(importSource) &&
|
|
512
|
+
options.namedImports &&
|
|
513
|
+
options.namedImports.includes(importName) &&
|
|
514
|
+
// TODO: handle options.replacement as an object
|
|
515
|
+
(typeof options.replacement === 'string' || options.replacement === null)
|
|
516
|
+
) {
|
|
517
|
+
importRestrictions.push({
|
|
518
|
+
importName,
|
|
519
|
+
replacement: options.replacement,
|
|
520
|
+
pattern,
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
break // Only use the first matching restriction for an import
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
})
|
|
396
527
|
|
|
397
|
-
|
|
528
|
+
if (importRestrictions.length === 0) {
|
|
529
|
+
return
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
// Report separate errors for each restricted import
|
|
533
|
+
importRestrictions.forEach((restriction) => {
|
|
398
534
|
context.report({
|
|
399
535
|
node,
|
|
400
|
-
messageId:
|
|
401
|
-
typeof restrictedPackageOptions.replacement === 'string'
|
|
402
|
-
? 'ImportedNameRestrictionWithReplacement'
|
|
403
|
-
: 'ImportedNameRestriction',
|
|
536
|
+
messageId: restriction.replacement ? 'ImportedNameRestrictionWithReplacement' : 'ImportedNameRestriction',
|
|
404
537
|
data: {
|
|
405
|
-
importedName:
|
|
538
|
+
importedName: restriction.importName,
|
|
406
539
|
name: importSource,
|
|
407
|
-
replacement:
|
|
540
|
+
replacement: restriction.replacement,
|
|
408
541
|
},
|
|
409
|
-
fix:
|
|
410
|
-
context,
|
|
411
|
-
node,
|
|
412
|
-
restrictedPackageOptions.namedImports,
|
|
413
|
-
restrictedPackageOptions.replacement,
|
|
414
|
-
),
|
|
542
|
+
fix: restriction.replacement ? createMultiNamedImportReplacer(context, node, importRestrictions) : null,
|
|
415
543
|
})
|
|
416
544
|
})
|
|
417
545
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-restrict-replace-import",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "ESLint Plugin for Restricting and Replacing Import",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"lint:js": "eslint .",
|
|
27
27
|
"test": "mocha tests --recursive",
|
|
28
28
|
"update:eslint-docs": "eslint-doc-generator",
|
|
29
|
-
"
|
|
29
|
+
"release": "npm run lint && npm run test && npm publish"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"requireindex": "^1.2.0"
|