Skip to content

Add wildcard pattern support for core modules #3200

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
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