diff --git a/package-lock.json b/package-lock.json index 7bf7e2d..4c76b51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "fd-package-json": "^1.2.0", + "module-replacements": "^2.0.0", "semver": "^7.6.0" }, "devDependencies": { @@ -1665,6 +1666,11 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/module-replacements": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/module-replacements/-/module-replacements-2.0.0.tgz", + "integrity": "sha512-v5KRQeCk4aTIPZtphSXrRJa6cfOLGY6ex1IihWYwlEaIHWUr1vgkcZ5k4AxYTE36gtZIzxo8q2w298E5Gt31sQ==" + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", diff --git a/package.json b/package.json index 8082b5d..8215307 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ }, "dependencies": { "fd-package-json": "^1.2.0", + "module-replacements": "^2.0.0", "semver": "^7.6.0" } } diff --git a/src/replacements.ts b/src/replacements.ts deleted file mode 100644 index 318f022..0000000 --- a/src/replacements.ts +++ /dev/null @@ -1,327 +0,0 @@ -interface ReplacementLike { - moduleName: string; -} - -export interface SimpleReplacement extends ReplacementLike { - type: 'simple'; - replacement: string; -} - -export interface NativeReplacement extends ReplacementLike { - type: 'native'; - mdnPath: string; - nodeVersion: string; - replacement: string; -} - -export interface DocumentedReplacement extends ReplacementLike { - type: 'documented'; - docPath: string; - moduleName: string; -} - -export interface NoReplacement extends ReplacementLike { - type: 'none'; -} - -export type Replacement = - | NativeReplacement - | DocumentedReplacement - | SimpleReplacement - | NoReplacement; - -export const preferredReplacements: Replacement[] = [ - { - type: 'documented', - moduleName: 'npm-run-all', - docPath: 'npm-run-all' - }, - { - type: 'documented', - moduleName: 'cpx', - docPath: 'cpx' - }, - { - type: 'documented', - moduleName: 'is-builtin-module', - docPath: 'is-builtin-module' - }, - { - type: 'documented', - moduleName: 'builtin-modules', - docPath: 'is-builtin-module' - }, - { - type: 'documented', - moduleName: 'eslint-plugin-react', - docPath: 'eslint-plugin-react' - }, - { - type: 'documented', - moduleName: 'eslint-plugin-import', - docPath: 'eslint-plugin-import' - }, - { - type: 'documented', - moduleName: 'eslint-plugin-node', - docPath: 'eslint-plugin-node' - } -]; - -export const microUtilities: Replacement[] = [ - { - type: 'simple', - moduleName: 'is-number', - replacement: `Use typeof v === 'number'` - }, - { - type: 'simple', - moduleName: 'is-plain-object', - replacement: `Use typeof v === 'object' && v !== null && v.constructor === Object` - }, - { - type: 'simple', - moduleName: 'is-primitve', - replacement: `Use v === null || (typeof v !== 'function' && typeof v !== 'object')` - }, - { - type: 'simple', - moduleName: 'is-regexp', - replacement: `Use v instanceof RegExp, or if cross-realm, use Object.prototype.toString.call(v) === '[object RegExp]'` - }, - { - type: 'simple', - moduleName: 'is-travis', - replacement: `Use ('TRAVIS' in process.env)` - }, - { - type: 'simple', - moduleName: 'is-npm', - replacement: `Use process.env.npm_config_user_agent?.startsWith('npm')` - }, - { - type: 'simple', - moduleName: 'clone-regexp', - replacement: `Use new RegExp(regexpToCopy)` - }, - { - type: 'simple', - moduleName: 'split-lines', - replacement: `Use str.split(/\\r?\\n/)` - }, - { - type: 'simple', - moduleName: 'is-windows', - replacement: `Use process.platform === 'win32'` - }, - { - type: 'simple', - moduleName: 'is-whitespace', - replacement: `Use str.trim() === '' or /^\s*$/.test(str)` - }, - { - type: 'simple', - moduleName: 'is-string', - replacement: `Use typeof str === 'string'` - }, - {type: 'simple', moduleName: 'is-odd', replacement: `Use (n % 2) === 1`}, - {type: 'simple', moduleName: 'is-even', replacement: `Use (n % 2) === 0`} -]; - -export const nativeReplacements: Replacement[] = [ - { - type: 'native', - moduleName: 'object.entries', - nodeVersion: '7.0.0', - replacement: 'Object.entries', - mdnPath: 'Global_Objects/Object/entries' - }, - { - type: 'native', - moduleName: 'date', - nodeVersion: '0.10.0', - replacement: 'Date', - mdnPath: 'Global_Objects/Date' - }, - { - type: 'native', - moduleName: 'for-each', - nodeVersion: '0.12.0', - replacement: 'for...of (using `Object.entries` if dealing with objects)', - mdnPath: 'Statements/for...of' - }, - { - type: 'native', - moduleName: 'array.of', - nodeVersion: '4.0.0', - replacement: 'Array.of', - mdnPath: 'Global_Objects/Array/of' - }, - { - type: 'native', - moduleName: 'number.isnan', - nodeVersion: '0.10.0', - replacement: 'Number.isNaN', - mdnPath: 'Global_Objects/Number/isNaN' - }, - { - type: 'native', - moduleName: 'array.prototype.findindex', - nodeVersion: '4.0.0', - replacement: 'Array.prototype.findIndex', - mdnPath: 'Global_Objects/Array/findIndex' - }, - { - type: 'native', - moduleName: 'array.from', - nodeVersion: '4.0.0', - replacement: 'Array.from', - mdnPath: 'Global_Objects/Array/from' - }, - { - type: 'native', - moduleName: 'object-is', - nodeVersion: '0.10.0', - replacement: 'Object.is', - mdnPath: 'Global_Objects/Object/is' - }, - { - type: 'native', - moduleName: 'hasown', - nodeVersion: '0.10.0', - replacement: - 'Object.prototype.hasOwnProperty.call(obj, prop) (or in later versions of node, `Object.hasOwn(obj, prop)`)', - mdnPath: 'Global_Objects/Object/hasOwnProperty' - }, - { - type: 'native', - moduleName: 'has-own-prop', - nodeVersion: '0.10.0', - replacement: - 'Object.prototype.hasOwnProperty.call(obj, prop) (or in later versions of node, `Object.hasOwn(obj, prop)`)', - mdnPath: 'Global_Objects/Object/hasOwnProperty' - }, - { - type: 'native', - moduleName: 'array-map', - nodeVersion: '0.10.0', - replacement: 'Array.prototype.map', - mdnPath: 'Global_Objects/Array/map' - }, - { - type: 'native', - moduleName: 'is-nan', - nodeVersion: '0.10.0', - replacement: 'Number.isNaN', - mdnPath: 'Global_Objects/Number/isNaN' - }, - { - type: 'native', - moduleName: 'node.extend', - nodeVersion: '4.0.0', - replacement: - 'Object.assign, or if deep clones are needed, use structuredClone', - mdnPath: 'Global_Objects/Object/assign' - }, - { - type: 'native', - moduleName: 'extend-shallow', - nodeVersion: '4.0.0', - replacement: - 'Object.assign, or if deep clones are needed, use structuredClone', - mdnPath: 'Global_Objects/Object/assign' - }, - { - type: 'native', - moduleName: 'xtend', - nodeVersion: '4.0.0', - replacement: - 'Object.assign, or if deep clones are needed, use structuredClone', - mdnPath: 'Global_Objects/Object/assign' - }, - { - type: 'native', - moduleName: 'defaults', - nodeVersion: '4.0.0', - replacement: - 'Object.assign, or if deep clones are needed, use structuredClone', - mdnPath: 'Global_Objects/Object/assign' - }, - { - type: 'native', - moduleName: 'function-bind', - nodeVersion: '0.10.0', - replacement: 'Function.prototype.bind', - mdnPath: 'Global_Objects/Function/bind' - }, - { - type: 'native', - moduleName: 'regexp.prototype.flags', - nodeVersion: '6.0.0', - replacement: 'RegExp.prototype.flags (e.g. `/foo/g.flags`)', - mdnPath: 'Global_Objects/RegExp/flags' - }, - { - type: 'native', - moduleName: 'array.prototype.find', - nodeVersion: '4.0.0', - replacement: 'Array.prototype.find', - mdnPath: 'Global_Objects/Array/find' - }, - { - type: 'native', - moduleName: 'object-keys', - nodeVersion: '0.10.0', - replacement: 'Object.keys(obj)', - mdnPath: 'Global_Objects/Object/keys' - }, - { - type: 'native', - moduleName: 'define-properties', - nodeVersion: '0.10.0', - replacement: 'Object.defineProperties', - mdnPath: 'Global_Objects/Object/defineProperties' - }, - { - type: 'native', - moduleName: 'left-pad', - nodeVersion: '8.0.0', - replacement: 'String.prototype.padStart', - mdnPath: 'Global_Objects/String/padStart' - }, - { - type: 'native', - moduleName: 'pad-left', - nodeVersion: '8.0.0', - replacement: 'String.prototype.padStart', - mdnPath: 'Global_Objects/String/padStart' - }, - { - type: 'native', - moduleName: 'filter-array', - nodeVersion: '0.10.0', - replacement: 'Array.prototype.filter', - mdnPath: 'Global_Objects/Array/filter' - }, - { - type: 'native', - moduleName: 'array-every', - nodeVersion: '0.10.0', - replacement: 'Array.prototype.every', - mdnPath: 'Global_Objects/Array/every' - }, - { - type: 'native', - moduleName: 'index-of', - nodeVersion: '0.10.0', - replacement: 'Array.prototype.indexOf', - mdnPath: 'Global_Objects/Array/indexOf' - }, - { - type: 'native', - moduleName: 'last-index-of', - nodeVersion: '0.10.0', - replacement: 'Array.prototype.lastIndexOf', - mdnPath: 'Global_Objects/Array/lastIndexOf' - } -]; diff --git a/src/rules/ban-dependencies.ts b/src/rules/ban-dependencies.ts index 6a9fcbf..95e170e 100644 --- a/src/rules/ban-dependencies.ts +++ b/src/rules/ban-dependencies.ts @@ -1,11 +1,11 @@ import {Rule} from 'eslint'; import {getDocsUrl} from '../util/rule-meta.js'; import { - microUtilities, + microUtilsReplacements, preferredReplacements, nativeReplacements, - Replacement -} from '../replacements.js'; + type ModuleReplacement +} from 'module-replacements'; import {createReplacementListener} from '../util/imports.js'; interface BanDependenciesOptions { @@ -13,10 +13,10 @@ interface BanDependenciesOptions { modules?: string[]; } -const availablePresets: Record = { - microutilities: microUtilities, - native: nativeReplacements, - preferred: preferredReplacements +const availablePresets: Record = { + microutilities: microUtilsReplacements.moduleReplacements, + native: nativeReplacements.moduleReplacements, + preferred: preferredReplacements.moduleReplacements }; const defaultPresets = ['microutilities', 'native', 'preferred']; @@ -64,7 +64,7 @@ export const rule: Rule.RuleModule = { }, create: (context) => { const options = context.options[0] as BanDependenciesOptions | undefined; - const replacements: Replacement[] = []; + const replacements: ModuleReplacement[] = []; const presets = options?.presets ?? defaultPresets; const modules = options?.modules; diff --git a/src/test/rules/ban-dependencies_test.ts b/src/test/rules/ban-dependencies_test.ts index 2bb4b45..4245522 100644 --- a/src/test/rules/ban-dependencies_test.ts +++ b/src/test/rules/ban-dependencies_test.ts @@ -84,7 +84,7 @@ ruleTester.run('ban-dependencies', rule, { messageId: 'simpleReplacement', data: { name: 'is-number', - replacement: `Use typeof v === 'number'` + replacement: `Use typeof v === "number"` } } ] @@ -98,7 +98,7 @@ ruleTester.run('ban-dependencies', rule, { messageId: 'simpleReplacement', data: { name: 'is-number', - replacement: `Use typeof v === 'number'` + replacement: `Use typeof v === "number"` } } ] @@ -112,7 +112,7 @@ ruleTester.run('ban-dependencies', rule, { messageId: 'simpleReplacement', data: { name: 'is-number', - replacement: `Use typeof v === 'number'` + replacement: `Use typeof v === "number"` } } ] @@ -127,7 +127,7 @@ ruleTester.run('ban-dependencies', rule, { messageId: 'simpleReplacement', data: { name: 'is-number', - replacement: `Use typeof v === 'number'` + replacement: `Use typeof v === "number"` } } ] diff --git a/src/util/imports.ts b/src/util/imports.ts index 19d947b..3a9e73b 100644 --- a/src/util/imports.ts +++ b/src/util/imports.ts @@ -1,9 +1,9 @@ import {Rule} from 'eslint'; import {TSESTree} from '@typescript-eslint/typescript-estree'; -import {Replacement} from '../replacements.js'; import {closestPackageSatisfiesNodeVersion} from './package-json.js'; import {getMdnUrl, getReplacementsDocUrl} from './rule-meta.js'; import type {AST as JsonESTree} from 'jsonc-eslint-parser'; +import type {ModuleReplacement} from 'module-replacements'; export type ImportListenerCallback = ( context: Rule.RuleContext, @@ -75,14 +75,14 @@ export function createImportListener( /** * Callback used for the replacement listener * @param {Rule.RuleContext} context ESLint context - * @param {Replacement[]} replacements List of replacements + * @param {ModuleReplacement[]} replacements List of replacements * @param {Rule.Node} node Node being traversed * @param {string} source Module being imported * @return {void} */ function replacementListenerCallback( context: Rule.RuleContext, - replacements: Replacement[], + replacements: ModuleReplacement[], node: Rule.Node, source: string ): void { @@ -183,12 +183,12 @@ const packageJsonLikePath = /(^|[/\\])package.json$/; /** * Creates a rule listener which finds replacements in imports/requires * @param {Rule.RuleContext} context ESLint context - * @param {Replacement[]} replacements List of replacements + * @param {ModuleReplacement[]} replacements List of replacements * @return {Rule.RuleListener} */ export function createReplacementListener( context: Rule.RuleContext, - replacements: Replacement[] + replacements: ModuleReplacement[] ): Rule.RuleListener { if (packageJsonLikePath.test(context.filename)) { return createPackageJsonListener(context, (context, node, name) =>