Skip to content
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
5 changes: 4 additions & 1 deletion src/core/importType.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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/;
Expand Down
55 changes: 54 additions & 1 deletion tests/src/core/importType.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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');
});
Expand Down Expand Up @@ -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);
});
});
17 changes: 17 additions & 0 deletions tests/src/rules/no-extraneous-dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"',
Expand Down
Loading