Skip to content

Commit fa36d49

Browse files
Xunnamiusljharb
authored andcommitted
[New] extensions: add `pathGroupOverrides to allow enforcement decision overrides based on specifier
1 parent 74c9763 commit fa36d49

File tree

3 files changed

+169
-3
lines changed

3 files changed

+169
-3
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
99
### Added
1010
- add [`enforce-node-protocol-usage`] rule and `import/node-version` setting ([#3024], thanks [@GoldStrikeArch] and [@sevenc-nanashi])
1111
- add TypeScript types ([#3097], thanks [@G-Rath])
12+
- [`extensions`]: add `pathGroupOverrides to allow enforcement decision overrides based on specifier ([#3105], thanks [@Xunnamius])
1213

1314
### Fixed
1415
- [`no-unused-modules`]: provide more meaningful error message when no .eslintrc is present ([#3116], thanks [@michaelfaith])
@@ -1170,6 +1171,7 @@ for info on changes for earlier releases.
11701171
[#3122]: https://github.com/import-js/eslint-plugin-import/pull/3122
11711172
[#3116]: https://github.com/import-js/eslint-plugin-import/pull/3116
11721173
[#3106]: https://github.com/import-js/eslint-plugin-import/pull/3106
1174+
[#3105]: https://github.com/import-js/eslint-plugin-import/pull/3105
11731175
[#3097]: https://github.com/import-js/eslint-plugin-import/pull/3097
11741176
[#3073]: https://github.com/import-js/eslint-plugin-import/pull/3073
11751177
[#3072]: https://github.com/import-js/eslint-plugin-import/pull/3072

src/rules/extensions.js

+47-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import path from 'path';
22

3+
import minimatch from 'minimatch';
34
import resolve from 'eslint-module-utils/resolve';
45
import { isBuiltIn, isExternalModule, isScoped } from '../core/importType';
56
import moduleVisitor from 'eslint-module-utils/moduleVisitor';
@@ -16,6 +17,26 @@ const properties = {
1617
pattern: patternProperties,
1718
checkTypeImports: { type: 'boolean' },
1819
ignorePackages: { type: 'boolean' },
20+
pathGroupOverrides: {
21+
type: 'array',
22+
items: {
23+
type: 'object',
24+
properties: {
25+
pattern: {
26+
type: 'string',
27+
},
28+
patternOptions: {
29+
type: 'object',
30+
},
31+
action: {
32+
type: 'string',
33+
enum: ['enforce', 'ignore'],
34+
},
35+
},
36+
additionalProperties: false,
37+
required: ['pattern', 'action'],
38+
},
39+
},
1940
},
2041
};
2142

@@ -54,6 +75,10 @@ function buildProperties(context) {
5475
if (obj.checkTypeImports !== undefined) {
5576
result.checkTypeImports = obj.checkTypeImports;
5677
}
78+
79+
if (obj.pathGroupOverrides !== undefined) {
80+
result.pathGroupOverrides = obj.pathGroupOverrides;
81+
}
5782
});
5883

5984
if (result.defaultConfig === 'ignorePackages') {
@@ -143,20 +168,39 @@ module.exports = {
143168
return false;
144169
}
145170

171+
function computeOverrideAction(pathGroupOverrides, path) {
172+
for (let i = 0, l = pathGroupOverrides.length; i < l; i++) {
173+
const { pattern, patternOptions, action } = pathGroupOverrides[i];
174+
if (minimatch(path, pattern, patternOptions || { nocomment: true })) {
175+
return action;
176+
}
177+
}
178+
}
179+
146180
function checkFileExtension(source, node) {
147181
// bail if the declaration doesn't have a source, e.g. "export { foo };", or if it's only partially typed like in an editor
148182
if (!source || !source.value) { return; }
149183

150184
const importPathWithQueryString = source.value;
151185

186+
// If not undefined, the user decided if rules are enforced on this import
187+
const overrideAction = computeOverrideAction(
188+
props.pathGroupOverrides || [],
189+
importPathWithQueryString,
190+
);
191+
192+
if (overrideAction === 'ignore') {
193+
return;
194+
}
195+
152196
// don't enforce anything on builtins
153-
if (isBuiltIn(importPathWithQueryString, context.settings)) { return; }
197+
if (!overrideAction && isBuiltIn(importPathWithQueryString, context.settings)) { return; }
154198

155199
const importPath = importPathWithQueryString.replace(/\?(.*)$/, '');
156200

157201
// don't enforce in root external packages as they may have names with `.js`.
158202
// Like `import Decimal from decimal.js`)
159-
if (isExternalRootModule(importPath)) { return; }
203+
if (!overrideAction && isExternalRootModule(importPath)) { return; }
160204

161205
const resolvedPath = resolve(importPath, context);
162206

@@ -174,7 +218,7 @@ module.exports = {
174218
if (!extension || !importPath.endsWith(`.${extension}`)) {
175219
// ignore type-only imports and exports
176220
if (!props.checkTypeImports && (node.importKind === 'type' || node.exportKind === 'type')) { return; }
177-
const extensionRequired = isUseOfExtensionRequired(extension, isPackage);
221+
const extensionRequired = isUseOfExtensionRequired(extension, !overrideAction && isPackage);
178222
const extensionForbidden = isUseOfExtensionForbidden(extension);
179223
if (extensionRequired && !extensionForbidden) {
180224
context.report({

tests/src/rules/extensions.js

+120
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,86 @@ describe('TypeScript', () => {
736736
],
737737
parser,
738738
}),
739+
740+
// pathGroupOverrides: no patterns match good bespoke specifiers
741+
test({
742+
code: `
743+
import { ErrorMessage as UpstreamErrorMessage } from '@black-flag/core/util';
744+
745+
import { $instances } from 'rootverse+debug:src.ts';
746+
import { $exists } from 'rootverse+bfe:src/symbols.ts';
747+
748+
import type { Entries } from 'type-fest';
749+
`,
750+
parser,
751+
options: [
752+
'always',
753+
{
754+
ignorePackages: true,
755+
checkTypeImports: true,
756+
pathGroupOverrides: [
757+
{
758+
pattern: 'multiverse{*,*/**}',
759+
action: 'enforce',
760+
},
761+
],
762+
},
763+
],
764+
}),
765+
// pathGroupOverrides: an enforce pattern matches good bespoke specifiers
766+
test({
767+
code: `
768+
import { ErrorMessage as UpstreamErrorMessage } from '@black-flag/core/util';
769+
770+
import { $instances } from 'rootverse+debug:src.ts';
771+
import { $exists } from 'rootverse+bfe:src/symbols.ts';
772+
773+
import type { Entries } from 'type-fest';
774+
`,
775+
parser,
776+
options: [
777+
'always',
778+
{
779+
ignorePackages: true,
780+
checkTypeImports: true,
781+
pathGroupOverrides: [
782+
{
783+
pattern: 'rootverse{*,*/**}',
784+
action: 'enforce',
785+
},
786+
],
787+
},
788+
],
789+
}),
790+
// pathGroupOverrides: an ignore pattern matches bad bespoke specifiers
791+
test({
792+
code: `
793+
import { ErrorMessage as UpstreamErrorMessage } from '@black-flag/core/util';
794+
795+
import { $instances } from 'rootverse+debug:src';
796+
import { $exists } from 'rootverse+bfe:src/symbols';
797+
798+
import type { Entries } from 'type-fest';
799+
`,
800+
parser,
801+
options: [
802+
'always',
803+
{
804+
ignorePackages: true,
805+
checkTypeImports: true,
806+
pathGroupOverrides: [
807+
{
808+
pattern: 'multiverse{*,*/**}',
809+
action: 'enforce',
810+
},
811+
{
812+
pattern: 'rootverse{*,*/**}',
813+
action: 'ignore',
814+
},
815+
],
816+
},
817+
],
818+
}),
739819
],
740820
invalid: [
741821
test({
@@ -756,6 +836,46 @@ describe('TypeScript', () => {
756836
],
757837
parser,
758838
}),
839+
840+
// pathGroupOverrides: an enforce pattern matches bad bespoke specifiers
841+
test({
842+
code: `
843+
import { ErrorMessage as UpstreamErrorMessage } from '@black-flag/core/util';
844+
845+
import { $instances } from 'rootverse+debug:src';
846+
import { $exists } from 'rootverse+bfe:src/symbols';
847+
848+
import type { Entries } from 'type-fest';
849+
`,
850+
parser,
851+
options: [
852+
'always',
853+
{
854+
ignorePackages: true,
855+
checkTypeImports: true,
856+
pathGroupOverrides: [
857+
{
858+
pattern: 'rootverse{*,*/**}',
859+
action: 'enforce',
860+
},
861+
{
862+
pattern: 'universe{*,*/**}',
863+
action: 'ignore',
864+
},
865+
],
866+
},
867+
],
868+
errors: [
869+
{
870+
message: 'Missing file extension for "rootverse+debug:src"',
871+
line: 4,
872+
},
873+
{
874+
message: 'Missing file extension for "rootverse+bfe:src/symbols"',
875+
line: 5,
876+
},
877+
],
878+
}),
759879
],
760880
});
761881
});

0 commit comments

Comments
 (0)