Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 56 additions & 1 deletion docs/rule-jsdoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ As a bonus feature, importing exports annotated with `@private` is always forbid
"indexLoophole": true,
"filenameLoophole": false,
"defaultImportability": "public", // "public" | "package" | "private"
"treatSelfReferenceAs": "external" // "internal" | "external"
"treatSelfReferenceAs": "external", // "internal" | "external"
"excludeSourcePatterns": ["generated/**/*"] // Array of glob patterns for source paths to exclude
}],
}
```
Expand Down Expand Up @@ -49,6 +50,7 @@ type JSDocRuleOptions = {
filenameLoophole: boolean;
defaultImportability: "public" | "pacakge" | "private";
treatSelfReferenceAs: "internal" | "external";
excludeSourcePatterns?: string[];
};
```

Expand Down Expand Up @@ -186,3 +188,56 @@ In the above example, `my-package/foo` is a self reference that connects to `src
When `treatSelfReferenceAs: external`, this import is always allowed even though `something` is a package-private export because it is treated like an import from an external package.

When `treatSelfReferenceAs: internal`, this import is disallowed because import from `my-package/foo` is treated like an import from `src/somewhere/foo.ts`.

### `excludeSourcePatterns`

_Default value: `[]`_

An array of glob patterns for source paths to exclude from the importability check. When importing from a module that matches one of these patterns, the import-access/jsdoc rule will not apply any restrictions, regardless of the JSDoc annotations or defaultImportability setting.

This is particularly useful for handling imports from auto-generated files that don't have proper JSDoc annotations.

The patterns use the [minimatch](https://github.com/isaacs/minimatch) library's glob syntax.

**Examples:**

```ts
// Example: Match by file path
// excludeSourcePatterns: ["src/types/**/*.d.ts"]
import { typeDefinition } from "../types/api"; // Allowed if the implementation is in a .d.ts file in the src/types directory
```

Additional examples:
```js
// Match all files in a particular directory
"src/generated/**"

// Match multiple extensions
"**/*.{generated,auto}.{ts,js}"

// Match files with specific naming patterns
"**/[a-z]*.auto.ts"

// Match specific type definition files
"src/**/*.d.ts"
```


**Next.js specific configuration:**

When working with Next.js projects, it's recommended to exclude the `.next` directory which contains auto-generated files:

```js
// In your .eslintrc.js
{
"rules": {
"import-access/jsdoc": ["error", {
// ... other options ...
"excludeSourcePatterns": [".next/**"]
}]
}
}
```

This will ensure that any imports from auto-generated files in the `.next` directory are not subject to import-access restrictions.

25 changes: 21 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"license": "MIT",
"dependencies": {
"@typescript-eslint/utils": "^8.4.0",
"minimatch": "^10.0.1",
"tsutils": "^3.21.0"
},
"devDependencies": {
Expand Down
45 changes: 45 additions & 0 deletions src/__tests__/exclude-patterns.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { getESLintTester } from "./fixtures/eslint";

const tester = getESLintTester();

it("Importing from generated package is disallowed by default", async () => {
const result = await tester.lintFile(
"src/exclude-patterns/generated-type-user.ts",
{
jsdoc: {
defaultImportability: "package",
},
},
);
expect(result).toMatchInlineSnapshot(`
Array [
Object {
"column": 10,
"endColumn": 19,
"endLine": 1,
"line": 1,
"message": "Cannot import a package-private export 'someValue'",
"messageId": "package",
"nodeType": "ImportSpecifier",
"ruleId": "import-access/jsdoc",
"severity": 2,
},
]
`);
});

it("Importing from generated package is allowed with excludeSourcePatterns targeting the file path (relative path)", async () => {
const result = await tester.lintFile(
"src/exclude-patterns/generated-type-user.ts",
{
jsdoc: {
defaultImportability: "package",
excludeSourcePatterns: [
// exclude the types file
"src/__tests__/fixtures/project/src/exclude-patterns/types/**",
],
},
},
);
expect(result).toMatchInlineSnapshot(`Array []`);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { someValue } from "generated-package";

console.log(someValue);
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module "generated-package" {
export const someValue: string;
}
21 changes: 21 additions & 0 deletions src/core/checkSymbolmportability.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { minimatch } from "minimatch";
import path from "path";
import { Program, Symbol } from "typescript";
import { assertNever } from "../utils/assertNever";
import { concatArrays } from "../utils/concatArrays";
Expand Down Expand Up @@ -29,6 +31,25 @@ export function checkSymbolImportability(
return;
}

// Get the actual file name of the exported declaration
const exporterFilename = decl.getSourceFile().fileName;

// Check if moduleSpecifier or exporter file path matches any of the excludeSourcePatterns
if (packageOptions.excludeSourcePatterns?.length) {
for (const pattern of packageOptions.excludeSourcePatterns) {
// Check actual file path
// Get relative path from the project root
const projectPath = program.getCurrentDirectory();
const relativePath = path.relative(projectPath, exporterFilename);

// Check if the file path matches the pattern
if (minimatch(relativePath, pattern, { dot: true })) {
// Skip importability check for this source
return;
}
}
}

// If declaration is from external module, treat as importable
if (program.isSourceFileFromExternalLibrary(decl.getSourceFile())) {
return;
Expand Down
16 changes: 16 additions & 0 deletions src/rules/jsdoc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export type JSDocRuleOptions = {
* the importability check.
*/
treatSelfReferenceAs: "internal" | "external";
/**
* Array of glob patterns for source paths to exclude from the importability check.
* Useful for excluding generated files or auto-generated type definitions.
*/
excludeSourcePatterns?: string[];
};

const jsdocRule: Omit<
Expand Down Expand Up @@ -70,6 +75,12 @@ const jsdocRule: Omit<
type: "string",
enum: ["external", "internal"],
},
excludeSourcePatterns: {
type: "array",
items: {
type: "string",
},
},
},
additionalProperties: false,
},
Expand All @@ -81,6 +92,7 @@ const jsdocRule: Omit<
filenameLoophole: false,
defaultImportability: "public",
treatSelfReferenceAs: "external",
excludeSourcePatterns: [],
},
],
create(context) {
Expand All @@ -94,13 +106,15 @@ const jsdocRule: Omit<
filenameLoophole,
defaultImportability,
treatSelfReferenceAs,
excludeSourcePatterns,
} = jsDocRuleDefaultOptions(options[0]);

const packageOptions: PackageOptions = {
indexLoophole,
filenameLoophole,
defaultImportability,
treatSelfReferenceAs,
excludeSourcePatterns,
};

return {
Expand Down Expand Up @@ -249,12 +263,14 @@ export function jsDocRuleDefaultOptions(
filenameLoophole = false,
defaultImportability = "public",
treatSelfReferenceAs = "external",
excludeSourcePatterns = [],
} = options || {};
return {
indexLoophole,
filenameLoophole,
defaultImportability,
treatSelfReferenceAs,
excludeSourcePatterns,
};
}

Expand Down
1 change: 1 addition & 0 deletions src/utils/isInPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type PackageOptions = {
readonly filenameLoophole: boolean;
readonly defaultImportability: "public" | "package" | "private";
readonly treatSelfReferenceAs: "internal" | "external";
readonly excludeSourcePatterns?: readonly string[];
};

// ../ or ../../ or ...
Expand Down
Loading