diff --git a/packages/comparisons/src/data.json b/packages/comparisons/src/data.json
index 072745993..13fc05576 100644
--- a/packages/comparisons/src/data.json
+++ b/packages/comparisons/src/data.json
@@ -16106,7 +16106,8 @@
],
"flint": {
"name": "restrictedImports",
- "plugin": "ts"
+ "plugin": "ts",
+ "status": "implemented"
},
"oxlint": [
{
diff --git a/packages/site/src/content/docs/rules/ts/restrictedImports.mdx b/packages/site/src/content/docs/rules/ts/restrictedImports.mdx
new file mode 100644
index 000000000..f76ae8654
--- /dev/null
+++ b/packages/site/src/content/docs/rules/ts/restrictedImports.mdx
@@ -0,0 +1,144 @@
+---
+description: "Restricts specified modules from being imported."
+title: "restrictedImports"
+topic: "rules"
+---
+
+import { Tabs, TabItem } from "@astrojs/starlight/components";
+import { RuleEquivalents } from "~/components/RuleEquivalents";
+import RuleSummary from "~/components/RuleSummary.astro";
+
+
+
+Some modules should not be used in certain parts of a codebase.
+This rule allows you to restrict specific modules from being imported, either by exact path or by glob pattern.
+It detects static `import` and `export ... from` declarations.
+
+## Examples
+
+
+
+
+```ts
+// With paths: ["lodash"]
+import _ from "lodash";
+
+// With paths: [{ name: "utils", importNames: ["dangerousHelper"] }]
+import { dangerousHelper } from "utils";
+
+// With patterns: ["internal/*"]
+import { secret } from "internal/auth";
+```
+
+
+
+
+```ts
+// With paths: ["lodash"]
+import { pick } from "lodash-es";
+
+// With paths: [{ name: "utils", importNames: ["dangerousHelper"] }]
+import { safeHelper } from "utils";
+
+// With patterns: ["internal/*"]
+import { auth } from "external/auth";
+```
+
+
+
+
+## Options
+
+### `paths`
+
+- Type: `Array`
+- Default: `[]`
+
+Exact module specifiers to restrict.
+Each entry can be a simple string or a configuration object.
+
+```jsonc
+{
+ "paths": [
+ // Simple: restrict the entire module
+ "lodash",
+
+ // Advanced: restrict specific imports
+ {
+ "name": "utils",
+ "importNames": ["dangerousHelper"],
+ "message": "Use safeHelper instead.",
+ },
+
+ // Allow only certain imports
+ {
+ "name": "react",
+ "allowImportNames": ["useState", "useEffect"],
+ },
+
+ // Allow type-only imports
+ {
+ "name": "internal-types",
+ "allowTypeImports": true,
+ },
+ ],
+}
+```
+
+**PathConfig fields:**
+
+| Field | Type | Description |
+| ------------------ | ----------- | --------------------------------------------------------------- |
+| `name` | `string` | The module specifier to restrict. |
+| `message` | `string?` | Custom message shown when the import is restricted. |
+| `importNames` | `string[]?` | Only these specific import names are restricted. |
+| `allowImportNames` | `string[]?` | Only these import names are allowed; all others are restricted. |
+| `allowTypeImports` | `boolean?` | Whether type-only imports are exempt from the restriction. |
+
+### `patterns`
+
+- Type: `Array`
+- Default: `[]`
+
+Glob patterns to match restricted module specifiers.
+
+```jsonc
+{
+ "patterns": [
+ // Simple: restrict by glob
+ "internal/*",
+
+ // Advanced: restrict with options
+ {
+ "group": ["legacy/**", "deprecated/**"],
+ "message": "These modules are deprecated. Use the new API.",
+ "allowTypeImports": true,
+ },
+ ],
+}
+```
+
+**PatternConfig fields:**
+
+| Field | Type | Description |
+| ------------------ | ----------- | --------------------------------------------------------------- |
+| `group` | `string[]` | Glob patterns to match module specifiers against. |
+| `message` | `string?` | Custom message shown when a matching import is restricted. |
+| `importNames` | `string[]?` | Only these specific import names are restricted. |
+| `allowImportNames` | `string[]?` | Only these import names are allowed; all others are restricted. |
+| `allowTypeImports` | `boolean?` | Whether type-only imports are exempt from the restriction. |
+
+## When Not To Use It
+
+If you do not need to restrict any modules from being imported, you do not need this rule.
+This rule requires explicit configuration to do anything, so it has no effect without `paths` or `patterns` options.
+You might consider using [Flint disable comments](/directives) and/or [configuration file disables](/configuration#files) for specific situations instead of completely disabling this rule.
+
+## Further Reading
+
+- [ESLint `no-restricted-imports`](https://eslint.org/docs/latest/rules/no-restricted-imports)
+- [TypeScript-ESLint `no-restricted-imports`](https://typescript-eslint.io/rules/no-restricted-imports)
+
+## Equivalents in Other Linters
+
+
diff --git a/packages/ts/src/plugin.ts b/packages/ts/src/plugin.ts
index 56f9f18bf..c151ee77f 100644
--- a/packages/ts/src/plugin.ts
+++ b/packages/ts/src/plugin.ts
@@ -241,6 +241,7 @@ import regexUnusedLazyQuantifiers from "./rules/regexUnusedLazyQuantifiers.ts";
import regexUnusedQuantifiers from "./rules/regexUnusedQuantifiers.ts";
import regexValidity from "./rules/regexValidity.ts";
import regexWordMatchers from "./rules/regexWordMatchers.ts";
+import restrictedImports from "./rules/restrictedImports.ts";
import returnAssignments from "./rules/returnAssignments.ts";
import selfAssignments from "./rules/selfAssignments.ts";
import sequences from "./rules/sequences.ts";
@@ -527,6 +528,7 @@ export const ts = createPlugin({
regexUnusedQuantifiers,
regexValidity,
regexWordMatchers,
+ restrictedImports,
returnAssignments,
selfAssignments,
sequences,
diff --git a/packages/ts/src/rules/restrictedImports.test.ts b/packages/ts/src/rules/restrictedImports.test.ts
new file mode 100644
index 000000000..32729b2ba
--- /dev/null
+++ b/packages/ts/src/rules/restrictedImports.test.ts
@@ -0,0 +1,390 @@
+import rule from "./restrictedImports.ts";
+import { ruleTester } from "./ruleTester.ts";
+
+ruleTester.describe(rule, {
+ invalid: [
+ {
+ code: `
+import foo from "forbidden";
+`,
+ options: {
+ paths: ["forbidden"],
+ },
+ snapshot: `
+import foo from "forbidden";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'forbidden' import is restricted from being used.
+`,
+ },
+ {
+ code: `
+import foo from "forbidden";
+`,
+ options: {
+ paths: [
+ {
+ message: "Use 'allowed-mod' instead.",
+ name: "forbidden",
+ },
+ ],
+ },
+ snapshot: `
+import foo from "forbidden";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'forbidden' import is restricted from being used. Use 'allowed-mod' instead.
+`,
+ },
+ {
+ code: `
+import { badExport } from "mod";
+`,
+ options: {
+ paths: [
+ {
+ importNames: ["badExport"],
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+import { badExport } from "mod";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'badExport' import from 'mod' is restricted.
+`,
+ },
+ {
+ code: `
+import foo from "mod";
+`,
+ options: {
+ paths: [
+ {
+ importNames: ["default"],
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+import foo from "mod";
+~~~~~~~~~~~~~~~~~~~~~~
+'default' import from 'mod' is restricted.
+`,
+ },
+ {
+ code: `
+import * as ns from "mod";
+`,
+ options: {
+ paths: [
+ {
+ importNames: ["badExport"],
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+import * as ns from "mod";
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+* import is invalid because 'badExport' from 'mod' is restricted.
+`,
+ },
+ {
+ code: `
+import "forbidden";
+`,
+ options: {
+ paths: ["forbidden"],
+ },
+ snapshot: `
+import "forbidden";
+~~~~~~~~~~~~~~~~~~~
+'forbidden' import is restricted from being used.
+`,
+ },
+ {
+ code: `
+import { notAllowed } from "mod";
+`,
+ options: {
+ paths: [
+ {
+ allowImportNames: ["allowed"],
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+import { notAllowed } from "mod";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'notAllowed' import from 'mod' is restricted.
+`,
+ },
+ {
+ code: `
+import foo from "internal/secret";
+`,
+ options: {
+ patterns: ["internal/*"],
+ },
+ snapshot: `
+import foo from "internal/secret";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'internal/secret' import is restricted from being used by a pattern.
+`,
+ },
+ {
+ code: `
+import foo from "internal/secret";
+`,
+ options: {
+ patterns: [
+ {
+ group: ["internal/*"],
+ message: "Do not import from internal modules.",
+ },
+ ],
+ },
+ snapshot: `
+import foo from "internal/secret";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'internal/secret' import is restricted from being used by a pattern. Do not import from internal modules.
+`,
+ },
+ {
+ code: `
+import { badName } from "utils/helpers";
+`,
+ options: {
+ patterns: [
+ {
+ group: ["utils/*"],
+ importNames: ["badName"],
+ },
+ ],
+ },
+ snapshot: `
+import { badName } from "utils/helpers";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'badName' import from 'utils/helpers' is restricted.
+`,
+ },
+ {
+ code: `
+export { foo } from "forbidden";
+`,
+ options: {
+ paths: ["forbidden"],
+ },
+ snapshot: `
+export { foo } from "forbidden";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'forbidden' import is restricted from being used.
+`,
+ },
+ {
+ code: `
+export * from "forbidden";
+`,
+ options: {
+ paths: ["forbidden"],
+ },
+ snapshot: `
+export * from "forbidden";
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+'forbidden' import is restricted from being used.
+`,
+ },
+ {
+ code: `
+import { foo } from "mod";
+`,
+ options: {
+ paths: [
+ {
+ allowTypeImports: true,
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+import { foo } from "mod";
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+'mod' import is restricted from being used.
+`,
+ },
+ {
+ code: `
+import { type A, b } from "mod";
+`,
+ options: {
+ paths: [
+ {
+ allowTypeImports: true,
+ importNames: ["A", "b"],
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+import { type A, b } from "mod";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'b' import from 'mod' is restricted.
+`,
+ },
+ {
+ code: `
+import { badExport } from "mod";
+`,
+ options: {
+ paths: [
+ {
+ importNames: ["badExport"],
+ message: "Use goodExport instead.",
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+import { badExport } from "mod";
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+'badExport' import from 'mod' is restricted. Use goodExport instead.
+`,
+ },
+ {
+ code: `
+import * as ns from "mod";
+`,
+ options: {
+ paths: [
+ {
+ importNames: ["a", "b"],
+ message: "Import specific allowed names.",
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+import * as ns from "mod";
+~~~~~~~~~~~~~~~~~~~~~~~~~~
+* import is invalid because 'a', 'b' from 'mod' is restricted. Import specific allowed names.
+`,
+ },
+ {
+ code: `
+export * from "mod";
+`,
+ options: {
+ paths: [
+ {
+ importNames: ["secret"],
+ name: "mod",
+ },
+ ],
+ },
+ snapshot: `
+export * from "mod";
+~~~~~~~~~~~~~~~~~~~~
+* import is invalid because 'secret' from 'mod' is restricted.
+`,
+ },
+ ],
+ valid: [
+ {
+ code: `import foo from "allowed";`,
+ options: {
+ paths: ["forbidden"],
+ },
+ },
+ {
+ code: `import type { Foo } from "mod";`,
+ options: {
+ paths: [
+ {
+ allowTypeImports: true,
+ name: "mod",
+ },
+ ],
+ },
+ },
+ {
+ code: `import { allowed } from "mod";`,
+ options: {
+ paths: [
+ {
+ allowImportNames: ["allowed"],
+ name: "mod",
+ },
+ ],
+ },
+ },
+ {
+ code: `import { goodExport } from "mod";`,
+ options: {
+ paths: [
+ {
+ importNames: ["badExport"],
+ name: "mod",
+ },
+ ],
+ },
+ },
+ {
+ code: `import foo from "external/lib";`,
+ options: {
+ patterns: ["internal/*"],
+ },
+ },
+ `import foo from "anything";`,
+ {
+ code: `import "mod";`,
+ options: {
+ paths: [
+ {
+ importNames: ["badExport"],
+ name: "mod",
+ },
+ ],
+ },
+ },
+ {
+ code: `import { type A } from "mod";`,
+ options: {
+ paths: [
+ {
+ allowTypeImports: true,
+ importNames: ["A"],
+ name: "mod",
+ },
+ ],
+ },
+ },
+ {
+ code: `const foo = 1; export { foo };`,
+ options: {
+ paths: ["forbidden"],
+ },
+ },
+ {
+ code: `import { allowed } from "utils/helpers";`,
+ options: {
+ patterns: [
+ {
+ allowImportNames: ["allowed"],
+ group: ["utils/*"],
+ },
+ ],
+ },
+ },
+ {
+ code: `export type { Foo } from "mod";`,
+ options: {
+ paths: [
+ {
+ allowTypeImports: true,
+ name: "mod",
+ },
+ ],
+ },
+ },
+ ],
+});
diff --git a/packages/ts/src/rules/restrictedImports.ts b/packages/ts/src/rules/restrictedImports.ts
new file mode 100644
index 000000000..5301118b8
--- /dev/null
+++ b/packages/ts/src/rules/restrictedImports.ts
@@ -0,0 +1,523 @@
+import {
+ getTSNodeRange,
+ typescriptLanguage,
+} from "@flint.fyi/typescript-language";
+import ts, { SyntaxKind } from "typescript";
+import { z } from "zod";
+
+import { ruleCreator } from "./ruleCreator.ts";
+
+const pathConfigSchema = z.object({
+ allowImportNames: z
+ .array(z.string())
+ .optional()
+ .describe("Import names that are explicitly allowed from this module."),
+ allowTypeImports: z
+ .boolean()
+ .optional()
+ .describe("Whether type-only imports from this module are allowed."),
+ importNames: z
+ .array(z.string())
+ .optional()
+ .describe("Specific import names to restrict from this module."),
+ message: z
+ .string()
+ .optional()
+ .describe("A custom message to display when this module is restricted."),
+ name: z.string().describe("The module specifier to restrict."),
+});
+
+const patternConfigSchema = z.object({
+ allowImportNames: z
+ .array(z.string())
+ .optional()
+ .describe(
+ "Import names that are explicitly allowed from matching modules.",
+ ),
+ allowTypeImports: z
+ .boolean()
+ .optional()
+ .describe("Whether type-only imports from matching modules are allowed."),
+ group: z
+ .array(z.string())
+ .describe("Glob patterns to match module specifiers against."),
+ importNames: z
+ .array(z.string())
+ .optional()
+ .describe("Specific import names to restrict from matching modules."),
+ message: z
+ .string()
+ .optional()
+ .describe(
+ "A custom message to display when a matching module is restricted.",
+ ),
+});
+
+interface ImportedName {
+ isTypeOnly: boolean;
+ name: string;
+}
+
+interface NormalizedPathConfig {
+ allowImportNames?: string[] | undefined;
+ allowTypeImports?: boolean | undefined;
+ importNames?: string[] | undefined;
+ message?: string | undefined;
+}
+
+interface NormalizedPatternConfig {
+ allowImportNames?: string[] | undefined;
+ allowTypeImports?: boolean | undefined;
+ group: RegExp[];
+ importNames?: string[] | undefined;
+ message?: string | undefined;
+}
+
+function globToRegExp(pattern: string) {
+ const escaped = pattern
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
+ .replace(/\*\*/g, "\0")
+ .replace(/\*/g, "[^/]*")
+ .replace(/\?/g, "[^/]")
+ .replace(/\0/g, ".*");
+ return new RegExp(`^${escaped}$`);
+}
+
+function hasNameRestrictions(config: {
+ allowImportNames?: string[] | undefined;
+ importNames?: string[] | undefined;
+}) {
+ return Boolean(config.importNames ?? config.allowImportNames);
+}
+
+function isNameRestricted(
+ name: string,
+ config: {
+ allowImportNames?: string[] | undefined;
+ importNames?: string[] | undefined;
+ },
+) {
+ if (config.importNames) {
+ return config.importNames.includes(name);
+ }
+
+ if (config.allowImportNames) {
+ return !config.allowImportNames.includes(name);
+ }
+
+ return true;
+}
+
+export default ruleCreator.createRule(typescriptLanguage, {
+ about: {
+ description: "Restricts specified modules from being imported.",
+ id: "restrictedImports",
+ },
+ messages: {
+ everythingRestricted: {
+ primary:
+ "* import is invalid because '{{ importNames }}' from '{{ source }}' is restricted.",
+ secondary: [
+ "This import uses a namespace or wildcard import, but specific names from this module are restricted.",
+ "Consider importing only the allowed names explicitly.",
+ ],
+ suggestions: [
+ "Replace the namespace import with named imports that are allowed.",
+ ],
+ },
+ everythingRestrictedWithMessage: {
+ primary:
+ "* import is invalid because '{{ importNames }}' from '{{ source }}' is restricted. {{ customMessage }}",
+ secondary: [
+ "This import uses a namespace or wildcard import, but specific names from this module are restricted.",
+ ],
+ suggestions: [
+ "Replace the namespace import with named imports that are allowed.",
+ ],
+ },
+ importNameRestricted: {
+ primary: "'{{ importName }}' import from '{{ source }}' is restricted.",
+ secondary: [
+ "This specific import name has been restricted by project configuration.",
+ "Consider using an alternative API or module.",
+ ],
+ suggestions: [
+ "Remove this import or replace it with an allowed alternative.",
+ ],
+ },
+ importNameRestrictedWithMessage: {
+ primary:
+ "'{{ importName }}' import from '{{ source }}' is restricted. {{ customMessage }}",
+ secondary: [
+ "This specific import name has been restricted by project configuration.",
+ ],
+ suggestions: [
+ "Remove this import or replace it with an allowed alternative.",
+ ],
+ },
+ pathRestricted: {
+ primary: "'{{ source }}' import is restricted from being used.",
+ secondary: [
+ "This module has been restricted by project configuration.",
+ "Consider using an alternative module.",
+ ],
+ suggestions: [
+ "Remove this import or replace it with an allowed alternative.",
+ ],
+ },
+ pathRestrictedWithMessage: {
+ primary:
+ "'{{ source }}' import is restricted from being used. {{ customMessage }}",
+ secondary: ["This module has been restricted by project configuration."],
+ suggestions: [
+ "Remove this import or replace it with an allowed alternative.",
+ ],
+ },
+ patternRestricted: {
+ primary:
+ "'{{ source }}' import is restricted from being used by a pattern.",
+ secondary: [
+ "This module matches a restricted pattern in the project configuration.",
+ "Consider using an alternative module.",
+ ],
+ suggestions: [
+ "Remove this import or replace it with an allowed alternative.",
+ ],
+ },
+ patternRestrictedWithMessage: {
+ primary:
+ "'{{ source }}' import is restricted from being used by a pattern. {{ customMessage }}",
+ secondary: [
+ "This module matches a restricted pattern in the project configuration.",
+ ],
+ suggestions: [
+ "Remove this import or replace it with an allowed alternative.",
+ ],
+ },
+ },
+ options: {
+ paths: z
+ .array(z.union([z.string(), pathConfigSchema]))
+ .default([])
+ .describe("Exact module specifiers to restrict."),
+ patterns: z
+ .array(z.union([z.string(), patternConfigSchema]))
+ .default([])
+ .describe("Glob patterns to match restricted module specifiers."),
+ },
+ setup(context) {
+ const state = {
+ initialized: false,
+ normalizedPatterns: [] as NormalizedPatternConfig[],
+ pathMap: new Map(),
+ };
+
+ function ensureInitialized(options: unknown) {
+ if (state.initialized) {
+ return;
+ }
+
+ state.initialized = true;
+
+ const { paths, patterns } = options as {
+ paths: (
+ | string
+ | {
+ allowImportNames?: string[] | undefined;
+ allowTypeImports?: boolean | undefined;
+ importNames?: string[] | undefined;
+ message?: string | undefined;
+ name: string;
+ }
+ )[];
+ patterns: (
+ | string
+ | {
+ allowImportNames?: string[] | undefined;
+ allowTypeImports?: boolean | undefined;
+ group: string[];
+ importNames?: string[] | undefined;
+ message?: string | undefined;
+ }
+ )[];
+ };
+
+ for (const pathEntry of paths) {
+ if (typeof pathEntry === "string") {
+ const existing = state.pathMap.get(pathEntry) ?? [];
+ existing.push({});
+ state.pathMap.set(pathEntry, existing);
+ } else {
+ const existing = state.pathMap.get(pathEntry.name) ?? [];
+ existing.push({
+ allowImportNames: pathEntry.allowImportNames,
+ allowTypeImports: pathEntry.allowTypeImports,
+ importNames: pathEntry.importNames,
+ message: pathEntry.message,
+ });
+ state.pathMap.set(pathEntry.name, existing);
+ }
+ }
+
+ for (const patternEntry of patterns) {
+ if (typeof patternEntry === "string") {
+ state.normalizedPatterns.push({
+ group: [globToRegExp(patternEntry)],
+ });
+ } else {
+ state.normalizedPatterns.push({
+ allowImportNames: patternEntry.allowImportNames,
+ allowTypeImports: patternEntry.allowTypeImports,
+ group: patternEntry.group.map(globToRegExp),
+ importNames: patternEntry.importNames,
+ message: patternEntry.message,
+ });
+ }
+ }
+ }
+
+ function checkNode(
+ source: string,
+ names: ImportedName[],
+ range: { begin: number; end: number },
+ ) {
+ const pathConfigs = state.pathMap.get(source);
+ if (pathConfigs) {
+ for (const config of pathConfigs) {
+ if (config.allowTypeImports && names.every((n) => n.isTypeOnly)) {
+ continue;
+ }
+
+ if (!hasNameRestrictions(config)) {
+ context.report({
+ data: {
+ customMessage: config.message ?? "",
+ source,
+ },
+ message: config.message
+ ? "pathRestrictedWithMessage"
+ : "pathRestricted",
+ range,
+ });
+ continue;
+ }
+
+ for (const imported of names) {
+ if (config.allowTypeImports && imported.isTypeOnly) {
+ continue;
+ }
+
+ if (imported.name === "*") {
+ context.report({
+ data: {
+ customMessage: config.message ?? "",
+ importNames: config.importNames?.join("', '") ?? "",
+ source,
+ },
+ message: config.message
+ ? "everythingRestrictedWithMessage"
+ : "everythingRestricted",
+ range,
+ });
+ continue;
+ }
+
+ if (isNameRestricted(imported.name, config)) {
+ context.report({
+ data: {
+ customMessage: config.message ?? "",
+ importName: imported.name,
+ source,
+ },
+ message: config.message
+ ? "importNameRestrictedWithMessage"
+ : "importNameRestricted",
+ range,
+ });
+ }
+ }
+ }
+ }
+
+ for (const pattern of state.normalizedPatterns) {
+ const matches = pattern.group.some((re) => re.test(source));
+ if (!matches) {
+ continue;
+ }
+
+ if (pattern.allowTypeImports && names.every((n) => n.isTypeOnly)) {
+ continue;
+ }
+
+ if (!hasNameRestrictions(pattern)) {
+ context.report({
+ data: {
+ customMessage: pattern.message ?? "",
+ source,
+ },
+ message: pattern.message
+ ? "patternRestrictedWithMessage"
+ : "patternRestricted",
+ range,
+ });
+ continue;
+ }
+
+ for (const imported of names) {
+ if (pattern.allowTypeImports && imported.isTypeOnly) {
+ continue;
+ }
+
+ if (imported.name === "*") {
+ context.report({
+ data: {
+ customMessage: pattern.message ?? "",
+ importNames: pattern.importNames?.join("', '") ?? "",
+ source,
+ },
+ message: pattern.message
+ ? "everythingRestrictedWithMessage"
+ : "everythingRestricted",
+ range,
+ });
+ continue;
+ }
+
+ if (isNameRestricted(imported.name, pattern)) {
+ context.report({
+ data: {
+ customMessage: pattern.message ?? "",
+ importName: imported.name,
+ source,
+ },
+ message: pattern.message
+ ? "importNameRestrictedWithMessage"
+ : "importNameRestricted",
+ range,
+ });
+ }
+ }
+ }
+ }
+
+ function reportSideEffectRestrictions(
+ source: string,
+ range: { begin: number; end: number },
+ ) {
+ const pathConfigs = state.pathMap.get(source);
+ if (pathConfigs) {
+ for (const config of pathConfigs) {
+ if (!hasNameRestrictions(config)) {
+ context.report({
+ data: {
+ customMessage: config.message ?? "",
+ source,
+ },
+ message: config.message
+ ? "pathRestrictedWithMessage"
+ : "pathRestricted",
+ range,
+ });
+ }
+ }
+ }
+
+ for (const pattern of state.normalizedPatterns) {
+ const matches = pattern.group.some((re) => re.test(source));
+ if (matches && !hasNameRestrictions(pattern)) {
+ context.report({
+ data: {
+ customMessage: pattern.message ?? "",
+ source,
+ },
+ message: pattern.message
+ ? "patternRestrictedWithMessage"
+ : "patternRestricted",
+ range,
+ });
+ }
+ }
+ }
+
+ return {
+ visitors: {
+ ExportDeclaration: (node, { options, sourceFile }) => {
+ if (
+ !node.moduleSpecifier ||
+ !ts.isStringLiteral(node.moduleSpecifier)
+ ) {
+ return;
+ }
+
+ ensureInitialized(options);
+
+ const source = node.moduleSpecifier.text;
+ const topLevelTypeOnly = node.isTypeOnly;
+ const range = getTSNodeRange(node, sourceFile);
+
+ let names: ImportedName[];
+ if (node.exportClause && ts.isNamedExports(node.exportClause)) {
+ names = node.exportClause.elements.map((element) => ({
+ isTypeOnly: topLevelTypeOnly || element.isTypeOnly,
+ name: element.propertyName
+ ? element.propertyName.text
+ : element.name.text,
+ }));
+ } else {
+ names = [{ isTypeOnly: topLevelTypeOnly, name: "*" }];
+ }
+
+ checkNode(source, names, range);
+ },
+ ImportDeclaration: (node, { options, sourceFile }) => {
+ if (!ts.isStringLiteral(node.moduleSpecifier)) {
+ return;
+ }
+
+ ensureInitialized(options);
+
+ const source = node.moduleSpecifier.text;
+ const range = getTSNodeRange(node, sourceFile);
+
+ // Side-effect import: import "mod"
+ if (!node.importClause) {
+ reportSideEffectRestrictions(source, range);
+ return;
+ }
+
+ const topLevelTypeOnly =
+ node.importClause.phaseModifier === SyntaxKind.TypeKeyword;
+ const names: ImportedName[] = [];
+
+ if (node.importClause.name) {
+ names.push({
+ isTypeOnly: topLevelTypeOnly,
+ name: "default",
+ });
+ }
+
+ const bindings = node.importClause.namedBindings;
+ if (bindings) {
+ if (ts.isNamedImports(bindings)) {
+ for (const element of bindings.elements) {
+ names.push({
+ isTypeOnly: topLevelTypeOnly || element.isTypeOnly,
+ name: element.propertyName
+ ? element.propertyName.text
+ : element.name.text,
+ });
+ }
+ } else {
+ names.push({
+ isTypeOnly: topLevelTypeOnly,
+ name: "*",
+ });
+ }
+ }
+
+ checkNode(source, names, range);
+ },
+ },
+ };
+ },
+});