diff --git a/README.md b/README.md index 885f34873c..1222bc22a1 100644 --- a/README.md +++ b/README.md @@ -404,6 +404,21 @@ core module: } ``` +Wildcard patterns are supported to match multiple modules, using `*` as a wildcard: + +```jsonc +// .eslintrc +{ + "settings": { + "import/core-modules": [ + "electron", + "@my-monorepo/*", // matches @my-monorepo/package-a, @my-monorepo/package-b, etc. + "@my-*/*", // matches @my-org/package, @my-company/package, etc. + ], + }, +} +``` + In Electron's specific case, there is a shared config named `electron` that specifies this for you. diff --git a/src/core/importType.js b/src/core/importType.js index 32e200f1de..f5fbf166b6 100644 --- a/src/core/importType.js +++ b/src/core/importType.js @@ -1,5 +1,6 @@ import { isAbsolute as nodeIsAbsolute, relative, resolve as nodeResolve } from 'path'; import isCoreModule from 'is-core-module'; +import minimatch from 'minimatch'; import resolve from 'eslint-module-utils/resolve'; import { getContextPackagePath } from './packagePath'; @@ -32,7 +33,9 @@ export function isBuiltIn(name, settings, path) { if (path || !name) { return false; } const base = baseModule(name); const extras = settings && settings['import/core-modules'] || []; - return isCoreModule(base) || extras.indexOf(base) > -1; + return isCoreModule(base) + || extras.indexOf(base) > -1 + || extras.some((pattern) => pattern.includes('*') && minimatch(base, pattern)); } const moduleRegExp = /^\w/; diff --git a/tests/src/core/importType.js b/tests/src/core/importType.js index c4dca866e2..aa698a5822 100644 --- a/tests/src/core/importType.js +++ b/tests/src/core/importType.js @@ -2,7 +2,7 @@ import { expect } from 'chai'; import * as path from 'path'; import isCoreModule from 'is-core-module'; -import importType, { isExternalModule, isScoped, isAbsolute } from 'core/importType'; +import importType, { isExternalModule, isScoped, isAbsolute, isBuiltIn } from 'core/importType'; import { testContext, testFilePath } from '../utils'; @@ -139,6 +139,50 @@ describe('importType(name)', function () { expect(importType('@org/foobar/some/path/to/resource.json', scopedContext)).to.equal('builtin'); }); + it("should return 'builtin' for wildcard patterns in core modules", function () { + // Test basic wildcard patterns + const wildcardContext = testContext({ 'import/core-modules': ['@my-monorepo/*'] }); + expect(importType('@my-monorepo/package-a', wildcardContext)).to.equal('builtin'); + expect(importType('@my-monorepo/package-b', wildcardContext)).to.equal('builtin'); + expect(importType('@my-monorepo/some-long-package-name', wildcardContext)).to.equal('builtin'); + + // Test that non-matching patterns return external + expect(importType('@other-org/package', wildcardContext)).to.equal('external'); + expect(importType('regular-package', wildcardContext)).to.equal('external'); + expect(importType('@my-monorepo-but-not-scoped/package', wildcardContext)).to.equal('external'); + }); + + it("should return 'builtin' for wildcard patterns with multiple wildcards", function () { + const multiWildcardContext = testContext({ 'import/core-modules': ['@my-*/*'] }); + expect(importType('@my-org/package', multiWildcardContext)).to.equal('builtin'); + expect(importType('@my-company/package', multiWildcardContext)).to.equal('builtin'); + expect(importType('@my-test/package', multiWildcardContext)).to.equal('builtin'); + + // Should not match different patterns + expect(importType('@other-org/package', multiWildcardContext)).to.equal('external'); + expect(importType('my-org/package', multiWildcardContext)).to.equal('external'); + }); + + it("should return 'builtin' for resources inside wildcard core modules", function () { + const wildcardContext = testContext({ 'import/core-modules': ['@my-monorepo/*'] }); + expect(importType('@my-monorepo/package-a/some/path/to/resource.json', wildcardContext)).to.equal('builtin'); + expect(importType('@my-monorepo/package-b/nested/module', wildcardContext)).to.equal('builtin'); + }); + + it('should support mixing exact matches and wildcards in core modules', function () { + const mixedContext = testContext({ 'import/core-modules': ['electron', '@my-monorepo/*', '@specific/package'] }); + + // Exact matches should work + expect(importType('electron', mixedContext)).to.equal('builtin'); + expect(importType('@specific/package', mixedContext)).to.equal('builtin'); + + // Wildcard matches should work + expect(importType('@my-monorepo/any-package', mixedContext)).to.equal('builtin'); + + // Non-matches should be external + expect(importType('@other/package', mixedContext)).to.equal('external'); + }); + it("should return 'external' for module from 'node_modules' with default config", function () { expect(importType('resolve', context)).to.equal('external'); }); @@ -283,4 +327,13 @@ describe('isAbsolute', () => { expect(() => isAbsolute(0)).not.to.throw(); expect(() => isAbsolute(NaN)).not.to.throw(); }); + + it('should use safe glob matching instead of regex construction', function () { + // Verify no dynamic regex patterns like [\\s\\S]*? are created + const context = testContext({ 'import/core-modules': ['@my-monorepo/*'] }); + // Valid patterns should work safely without regex construction + expect(isBuiltIn('@my-monorepo/package-a', context.settings, null)).to.equal(true); + expect(isBuiltIn('@my-monorepo/package-b', context.settings, null)).to.equal(true); + expect(isBuiltIn('@other-org/package', context.settings, null)).to.equal(false); + }); }); diff --git a/tests/src/rules/no-extraneous-dependencies.js b/tests/src/rules/no-extraneous-dependencies.js index 4a465eb39d..d32b4d1622 100644 --- a/tests/src/rules/no-extraneous-dependencies.js +++ b/tests/src/rules/no-extraneous-dependencies.js @@ -155,6 +155,23 @@ ruleTester.run('no-extraneous-dependencies', rule, { code: 'import "@generated/bar/and/sub/path"', settings: { 'import/core-modules': ['@generated/bar'] }, }), + // Test wildcard patterns in core-modules + test({ + code: 'import "@my-monorepo/package-a"', + settings: { 'import/core-modules': ['@my-monorepo/*'] }, + }), + test({ + code: 'import "@my-monorepo/package-b/nested/module"', + settings: { 'import/core-modules': ['@my-monorepo/*'] }, + }), + test({ + code: 'import "@my-org/any-package"', + settings: { 'import/core-modules': ['@my-*/*'] }, + }), + test({ + code: 'import "@namespace/any-package"', + settings: { 'import/core-modules': ['@namespace/*', 'specific-module'] }, + }), // check if "rxjs" dependency declaration fix the "rxjs/operators subpackage test({ code: 'import "rxjs/operators"',