Skip to content

Conversation

AmanL04
Copy link

@AmanL04 AmanL04 commented Sep 11, 2025

Add no-cross-module-repository-usage ESLint rule

Summary

Implements a custom ESLint rule to prevent cross-module repository usage in NestJS applications as requested in Linear issue FOR-1074. The rule enforces two main requirements:

  1. NestJS modules should not export repository classes in their exports array
  2. Classes should not import or inject repository classes from different modules

Review & Testing Checklist for Human

⚠️ 3 critical items to verify:

  • Test the rule against real NestJS code - Apply the rule to clipboard-health repository and verify it correctly identifies violations without false positives. The implementation has not been tested against actual repository patterns yet.
  • Validate repository detection logic - Verify that the patterns for identifying repository classes (names ending in "Repository"/"Repo" and files ending in ".repository.ts"/".repo.ts") correctly match your actual repository naming conventions.
  • Check module boundary detection - The rule determines "same module" vs "cross module" by checking if files contain @Module decorators. Verify this logic aligns with your actual module structure and doesn't miss edge cases.

Recommended Test Plan

  1. Apply the rule to clipboard-health repository temporarily
  2. Check for false positives in legitimate same-module repository usage
  3. Verify it catches the cross-module violations mentioned in FOR-1074
  4. Test edge cases like repositories used in tests, dynamic imports, etc.

Notes


Summary by cubic

Adds the no-cross-module-repository-usage ESLint rule to enforce NestJS module boundaries per FOR-1074. It blocks exporting repositories from modules and using repositories across modules.

  • New Features

    • Flags repository exports in @Module.exports.
    • Flags cross-module repository imports and injections (constructor params and @Inject).
    • Detects repositories by class names ending in Repository/Repo and files ending in .repository.ts/.repo.ts.
    • Enabled as error for .module.ts, .service.ts, .repository.ts, and .repo.ts.
    • Exported in eslint-plugin and documented.
  • Migration

    • Remove repository classes from module exports; export services instead.
    • Avoid importing/injecting repositories across modules; expose services for cross-module access.

- Implement custom ESLint rule to prevent cross-module repository usage in NestJS applications
- Rule detects repository exports in @module decorators and flags them as violations
- Rule detects cross-module repository imports and injections using constructor parameters and @Inject decorators
- Repository detection based on class names ending in 'Repository'/'Repo' and files ending in '.repository.ts'/'.repo.ts'
- Module boundaries determined by analyzing @module providers rather than directory structure
- Add rule to eslint-plugin exports and configure in eslint-config for .module.ts, .service.ts, .repository.ts, and .repo.ts files
- Update test expectations to include new rule configuration
- Follows existing patterns from enforce-ts-rest-in-controllers rule

Co-Authored-By: [email protected] <[email protected]>
Copy link
Contributor

Original prompt from aman.lodha
Can you look into the below task and share your plan to implement it? If you need a repository to apply the changes on, you can use `clipboard-health` repository but considering it's a new change which can apply to multiple repositories which are nestjs apps.

https://linear.app/clipboardhealth/issue/FOR-1074/add-a-linting-warning-to-not-be-able-to-use-repo-classes-of-other

Copy link
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR that start with 'DevinAI'.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment and CI monitoring

Copy link

coderabbitai bot commented Sep 11, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

Adds a new ESLint rule, no-cross-module-repository-usage, to the plugin and config, documents it, and updates tests. The config enables the rule for module and service/repository file patterns when outside the core-utils monorepo. The rule analyzes imports, @module exports, constructor params, and @Inject usage to flag cross-module repository access.

Changes

Cohort / File(s) Summary
ESLint config overrides
packages/eslint-config/src/index.js
Adds "@clipboard-health/no-cross-module-repository-usage": "error" to overrides for "/*.module.ts" and for "/.service.ts", "**/.repository.ts", "**/*.repo.ts" when isOutsideCoreUtilsMonorepo.
ESLint config tests
packages/eslint-config/src/index.spec.ts
Updates assertions to include the new rule in module overrides; confirms presence in service/repository override.
Plugin rule registration
packages/eslint-plugin/src/index.ts
Imports and registers "no-cross-module-repository-usage" in exported rules map.
New rule implementation
packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts
Implements rule detecting: repository exports from @module, cross-module repository imports, constructor TSParameterProperty injections, and @Inject-based injections. Tracks repository imports and module-file context. Adds messages and metadata.
Rule documentation
packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/README.md
Documents rule intent, detection heuristics, examples, and guidance.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant ESLint
  participant Parser as TS Parser
  participant Rule as no-cross-module-repository-usage
  participant File as Source File (module/service/repo)

  ESLint->>Parser: Parse File to AST
  ESLint->>Rule: Initialize rule context
  Note over Rule: Initialize state: repositoryImports = {}, isModuleFile = false

  loop Visit imports
    Rule->>File: ImportDeclaration
    alt Import path suggests repository
      Rule-->>Rule: Record local specifier -> import path
      alt Not in module file
        Rule-->>ESLint: report crossModuleRepositoryImport
      end
    end
  end

  alt @Module(...) decorator present
    Rule->>File: Decorator(@Module)
    Rule-->>Rule: isModuleFile = true
    Rule-->>ESLint: report if exports[] contains repository classes
  end

  alt Constructor in non-module file
    Rule->>File: MethodDefinition(constructor)
    Rule-->>ESLint: report if TSParameterProperty type is a recorded repository import
  end

  loop Detect @Inject in non-module file
    Rule->>File: Decorator(@Inject)
    Rule-->>ESLint: report if token references recorded repository import
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • piotrj
  • arvindcbh
  • rkarroll

Pre-merge checks (4 passed, 1 inconclusive)

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Linked Issues Check ❓ Inconclusive For [FOR-1074], the code implements the primary coding requirements: a new rule that detects and reports exported repository classes and cross-module repository imports/injections, the rule is implemented in the plugin and exported, and the eslint-config enables the rule for module/service/repository file patterns, which addresses objectives 1–3; however, I cannot verify from these changes whether CI is configured to run this check on branch changes (objective 4), and the rule's heuristic module/repository detection is noted in the PR as unvalidated against the real codebase. Confirm that CI linting runs with the updated eslint-config on branch builds and run the rule against the repository to validate heuristics and tune false positives before enabling branch-level enforcement.
✅ Passed checks (4 passed)
Check name Status Explanation
Title Check ✅ Passed The pull request title "feat(FOR-1074): add no-cross-module-repository-usage ESLint rule" concisely and accurately summarizes the primary change by naming the new rule and referencing the linked issue, making it clear to reviewers what the main intent is.
Out of Scope Changes Check ✅ Passed All modified files are directly related to adding and documenting the new lint rule (rule implementation, README, plugin export, eslint-config updates, and tests); I do not see unrelated or extraneous changes in this diff.
Description Check ✅ Passed The PR description directly describes the added ESLint rule, its enforcement points, testing checklist, and connection to FOR-1074, and it aligns with the changes made in the diff.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch devin/1757591267-prevent-cross-repo-class-usage

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile Summary

This PR introduces a new ESLint rule no-cross-module-repository-usage to enforce architectural boundaries in NestJS applications. The rule prevents cross-module repository usage by implementing two main restrictions: 1) NestJS modules cannot export repository classes in their exports array, and 2) classes cannot import or inject repository classes from different modules.

The implementation adds the rule to the @clipboard-health/eslint-plugin package and configures it in the ESLint config to run on module files (*.module.ts) and repository-related files (*.service.ts, *.repository.ts, *.repo.ts). The rule uses AST parsing to detect repository patterns based on naming conventions (names ending in "Repository"/"Repo" or file paths ending with ".repository.ts"/".repo.ts") and determines module boundaries by checking for @Module decorators.

The rule integrates with the existing ESLint plugin architecture, following the same pattern as enforce-ts-rest-in-controllers. It's conditionally applied only when the ESLint config is used outside the core-utils monorepo, making it available for downstream NestJS applications while not enforcing application-specific rules on the library itself. The implementation includes proper test coverage updates and README documentation.

Confidence score: 2/5

  • This PR has significant implementation issues that could cause false positives and missed violations in production
  • Score reflects complex AST parsing logic with logical flaws in module boundary detection and overly broad repository identification patterns
  • Pay close attention to the rule implementation in packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts as it contains several critical bugs that need fixing before deployment

5 files reviewed, 5 comments

Edit Code Review Bot Settings | Greptile

Comment on lines 9 to 10
const isRepositoryClass = (name: string): boolean =>
name.endsWith("Repository") || name.includes("Repo");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The name.includes("Repo") condition is too broad and will match classes like ReportGenerator or RepoManager that aren't repositories. Consider using name.endsWith("Repo") or a more specific pattern.

Suggested change
const isRepositoryClass = (name: string): boolean =>
name.endsWith("Repository") || name.includes("Repo");
const isRepositoryClass = (name: string): boolean =>
name.endsWith("Repository") || name.endsWith("Repo");


ImportDeclaration(node) {
const importPath = node.source.value;
if (isRepositoryFile(importPath) || importPath.includes("repository")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The condition importPath.includes("repository") is overly broad and will match any import path containing 'repository', including legitimate cross-module imports like utility functions. This could cause false positives.

Comment on lines 142 to 151
if (!isModuleFile) {
context.report({
node: spec,
messageId: "crossModuleRepositoryImport",
data: {
repositoryName: spec.imported.name,
importPath,
},
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: This logic reports ALL repository imports in non-module files as violations, but it should only report imports from DIFFERENT modules. Currently, it would flag legitimate same-module repository usage in service files.

Comment on lines 108 to 109
const repositoryImports = new Map<string, string>(); // name -> import path
let isModuleFile = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: The isModuleFile flag and repositoryImports map are shared across the entire file analysis, but ESLint rules should be stateless between different files. This could cause incorrect behavior when linting multiple files.

Comment on lines +8 to +11

1. **No Repository Exports**: NestJS modules should not export repository classes in their `exports` array
2. **No Cross-Module Repository Usage**: Classes should not import or inject repository classes from different modules

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: Repository detection patterns may be too broad. name.includes('Repo') could match unintended classes like 'Report' or 'Reproduce'. Consider using more specific patterns or making this configurable.

@AmanL04 AmanL04 marked this pull request as draft September 11, 2025 12:08
Copy link

nx-cloud bot commented Sep 11, 2025

View your CI Pipeline Execution ↗ for commit 8ba4d78

Command Status Duration Result
nx affected --configuration ci --parallel 3 --t... ✅ Succeeded 1m 14s View ↗

☁️ Nx Cloud last updated this comment at 2025-09-11 12:50:39 UTC

devin-ai-integration bot and others added 2 commits September 11, 2025 12:16
- Remove RuleContext import that was causing TypeScript compilation error
- Simplify rule to only check for repository exports in @module decorators
- Remove unused helper functions and cross-module import detection logic
- Update README to reflect simplified scope
- Fix repository detection to use endsWith('Repo') instead of includes('Repo')
- Remove unused isRepositoryFile function
- Inline helper function to avoid TypeScript any type issues

Co-Authored-By: [email protected] <[email protected]>
- Inline checkModuleExports logic directly into Decorator visitor
- Eliminates TypeScript any type usage that was causing ESLint errors

Co-Authored-By: [email protected] <[email protected]>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7 issues found across 5 files


Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Ask questions if you need clarification on any suggestion

React with 👍 or 👎 to teach cubic. Mention @cubic-dev-ai to give feedback, ask questions, or re-run the review.


- Class names ending in "Repository"
- Class names containing "Repo"
- Files ending in ".repo.ts" or ".repository.ts"
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Detection criteria wording is misleading; matching is based on the import path string and primarily on it containing "repository" when extensions are omitted.

Prompt for AI agents
Address the following comment on packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/README.md at line 69:

<comment>Detection criteria wording is misleading; matching is based on the import path string and primarily on it containing &quot;repository&quot; when extensions are omitted.</comment>

<file context>
@@ -0,0 +1,77 @@
+
+- Class names ending in &quot;Repository&quot;
+- Class names containing &quot;Repo&quot;
+- Files ending in &quot;.repo.ts&quot; or &quot;.repository.ts&quot;
+
+### Module Boundary Detection
</file context>
Suggested change
- Files ending in ".repo.ts" or ".repository.ts"
- Import paths containing "repository" or ending in ".repo.ts" or ".repository.ts" (when the extension is present in the import)

@Injectable()
export class SomeService {
constructor(
private readonly someRepository: SomeRepository, // ✅ Same module usage
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This example is misleading; the current rule will report this injection in a non-module file when SomeRepository is imported.

Prompt for AI agents
Address the following comment on packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/README.md at line 58:

<comment>This example is misleading; the current rule will report this injection in a non-module file when SomeRepository is imported.</comment>

<file context>
@@ -0,0 +1,77 @@
+@Injectable()
+export class SomeService {
+  constructor(
+    private readonly someRepository: SomeRepository, // ✅ Same module usage
+  ) {}
+}
</file context>


### Module Boundary Detection

The rule determines module boundaries by analyzing `@Module` decorator's `providers` array rather than directory structure. This ensures accurate detection of which repositories belong to each module.
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Module boundary detection description is incorrect; the rule does not analyze providers to determine repository ownership.

Prompt for AI agents
Address the following comment on packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/README.md at line 73:

<comment>Module boundary detection description is incorrect; the rule does not analyze providers to determine repository ownership.</comment>

<file context>
@@ -0,0 +1,77 @@
+
+### Module Boundary Detection
+
+The rule determines module boundaries by analyzing `@Module` decorator&#39;s `providers` array rather than directory structure. This ensures accurate detection of which repositories belong to each module.
+
+### When Not To Use
</file context>
Suggested change
The rule determines module boundaries by analyzing `@Module` decorator's `providers` array rather than directory structure. This ensures accurate detection of which repositories belong to each module.
The rule treats files decorated with `@Module` as module files and flags repository imports/injections only in non-module files; it does not analyze providers to determine module ownership.

This rule enforces two main requirements:

1. **No Repository Exports**: NestJS modules should not export repository classes in their `exports` array
2. **No Cross-Module Repository Usage**: Classes should not import or inject repository classes from different modules
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This requirement is inaccurate; the implementation forbids repository imports/injections in non-module files regardless of module ownership.

Prompt for AI agents
Address the following comment on packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/README.md at line 10:

<comment>This requirement is inaccurate; the implementation forbids repository imports/injections in non-module files regardless of module ownership.</comment>

<file context>
@@ -0,0 +1,77 @@
+This rule enforces two main requirements:
+
+1. **No Repository Exports**: NestJS modules should not export repository classes in their `exports` array
+2. **No Cross-Module Repository Usage**: Classes should not import or inject repository classes from different modules
+
+### Examples
</file context>
Fix with Cubic

) {
repositoryImports.set(spec.local.name, importPath);

if (!isModuleFile) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-module import detection uses only !isModuleFile (consumer file), so it incorrectly flags same-module imports; needs actual module boundary resolution.

Prompt for AI agents
Address the following comment on packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts at line 142:

<comment>Cross-module import detection uses only !isModuleFile (consumer file), so it incorrectly flags same-module imports; needs actual module boundary resolution.</comment>

<file context>
@@ -0,0 +1,172 @@
+            ) {
+              repositoryImports.set(spec.local.name, importPath);
+
+              if (!isModuleFile) {
+                context.report({
+                  node: spec,
</file context>


ImportDeclaration(node) {
const importPath = node.source.value;
if (isRepositoryFile(importPath) || importPath.includes("repository")) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over-broad repository file detection via includes("repository"); tighten to suffix checks (including extensionless forms) to reduce false positives.

Prompt for AI agents
Address the following comment on packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts at line 133:

<comment>Over-broad repository file detection via includes(&quot;repository&quot;); tighten to suffix checks (including extensionless forms) to reduce false positives.</comment>

<file context>
@@ -0,0 +1,172 @@
+
+      ImportDeclaration(node) {
+        const importPath = node.source.value;
+        if (isRepositoryFile(importPath) || importPath.includes(&quot;repository&quot;)) {
+          node.specifiers.forEach((spec) =&gt; {
+            if (
</file context>

import createRule from "../../createRule";

const isRepositoryClass = (name: string): boolean =>
name.endsWith("Repository") || name.includes("Repo");
Copy link

@cubic-dev-ai cubic-dev-ai bot Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repository class detection is too broad; includes("Repo") matches non-repository names (e.g., ReportService). Prefer suffix matching.

Prompt for AI agents
Address the following comment on packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts at line 10:

<comment>Repository class detection is too broad; includes(&quot;Repo&quot;) matches non-repository names (e.g., ReportService). Prefer suffix matching.</comment>

<file context>
@@ -0,0 +1,172 @@
+import createRule from &quot;../../createRule&quot;;
+
+const isRepositoryClass = (name: string): boolean =&gt;
+  name.endsWith(&quot;Repository&quot;) || name.includes(&quot;Repo&quot;);
+
+const isRepositoryFile = (filePath: string): boolean =&gt;
</file context>

- Remove extra blank lines
- Reformat long conditional statement for better readability
- Addresses pull-request check failure in CI

Co-Authored-By: [email protected] <[email protected]>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts (1)

1-173: Fix module-detection ordering, add tests, and cover Nest edge-cases.

  • Add tests: create packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.spec.ts and run the rule against real Nest code (e.g., packages/example-nestjs/src/app.module.ts). Include cases: module files vs non-module files, @Inject decorator, constructor injection, forwardRef(() => Repo), aliased imports, type-only imports, path-alias/deep imports, and exports/providers that are call expressions/identifiers.
  • Critical bug: ImportDeclaration handlers run before @module decorator detection → false positives for module files. Change to a two-pass approach (collect imports/repo candidates during traversal and perform cross-module checks on Program:exit), or detect module file earlier.
  • Improve resolution (avoid fragile string heuristics): resolve import specifiers to actual file/module (use the TypeScript ESLint import resolver / tsconfig paths + context.getFilename()) to determine module ownership rather than relying on importPath.includes("repository") or filename suffixes.
  • Handle AST edge cases: support forwardRef and CallExpression-wrapped providers/exports/inject arguments and non-Identifier array elements; don't assume elements are Identifiers.
  • Add CI coverage and run the new tests in the repo; consider warn→error rollout.
🧹 Nitpick comments (1)
packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts (1)

9-10: Consider using case-insensitive repository detection.

The current detection might miss repositories with variations in casing (e.g., "REPOSITORY", "repo").

Apply this diff for case-insensitive detection:

 const isRepositoryClass = (name: string): boolean =>
-  name.endsWith("Repository") || name.includes("Repo");
+  /Repository$/i.test(name) || /Repo/i.test(name);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 9fe5a69 and 61b9fdb.

📒 Files selected for processing (5)
  • packages/eslint-config/src/index.js (1 hunks)
  • packages/eslint-config/src/index.spec.ts (2 hunks)
  • packages/eslint-plugin/src/index.ts (1 hunks)
  • packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/README.md (1 hunks)
  • packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,ts,py,go,rb}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,ts,py,go,rb}: Use environment variables for secrets and configuration, never hardcode secrets in code
Always validate and sanitize user input on the server side
Use parameterized queries or ORM query builders to prevent SQL injection
Return appropriate HTTP status codes for all API responses
Log errors with sufficient context but avoid logging sensitive information
Use consistent error handling patterns (e.g., try/catch, error middleware)
Do not expose internal implementation details in API responses
Use dependency injection for services and repositories

Files:

  • packages/eslint-config/src/index.js
  • packages/eslint-plugin/src/index.ts
  • packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts
  • packages/eslint-config/src/index.spec.ts
**/*.{js,ts}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{js,ts}: Use async/await for asynchronous operations in Node.js backends
Prefer named exports over default exports in backend modules

Files:

  • packages/eslint-config/src/index.js
  • packages/eslint-plugin/src/index.ts
  • packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts
  • packages/eslint-config/src/index.spec.ts
**/*.ts

⚙️ CodeRabbit configuration file

**/*.ts:

  • URLs and request and response bodies must adhere to jsonapi.org's JSON:API specification.
  • The links, version, ext, profile, and meta members are not required.
  • Use only the data resource linkage for relationships.
  • The type member should be singular, not plural.

Files:

  • packages/eslint-plugin/src/index.ts
  • packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts
  • packages/eslint-config/src/index.spec.ts
**/*.{test,spec}.{js,ts,py,go,rb}

📄 CodeRabbit inference engine (.cursorrules)

Write unit tests for all business logic

Files:

  • packages/eslint-config/src/index.spec.ts
🧠 Learnings (1)
📚 Learning: 2025-05-30T14:31:47.558Z
Learnt from: arvindcbh
PR: ClipboardHealth/core-utils#131
File: packages/eslint-plugin/package.json:5-8
Timestamp: 2025-05-30T14:31:47.558Z
Learning: In the clipboard-health/eslint-plugin package, ESLint should not be declared as a peer dependency. The project relies on ESLint plugins being consumed through configuration files in host projects rather than explicit dependency declarations.

Applied to files:

  • packages/eslint-config/src/index.js
  • packages/eslint-config/src/index.spec.ts
🧬 Code graph analysis (3)
packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/README.md (3)
packages/eslint-plugin/src/lib/rules/require-http-module-factory/index.ts (2)
  • ImportDeclaration (65-76)
  • create (43-88)
packages/eslint-plugin/src/lib/rules/enforce-ts-rest-in-controllers/index.ts (3)
  • decorator (99-111)
  • ImportDeclaration (40-60)
  • create (27-161)
packages/eslint-plugin/src/lib/createRule.ts (1)
  • name (4-5)
packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts (1)
packages/eslint-plugin/src/lib/createRule.ts (1)
  • name (4-5)
packages/eslint-config/src/index.spec.ts (1)
packages/eslint-plugin/src/lib/rules/require-http-module-factory/index.ts (5)
  • ImportDeclaration (65-76)
  • ArrayExpression (78-86)
  • spec (8-11)
  • element (47-62)
  • create (43-88)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (7)
packages/eslint-plugin/src/index.ts (1)

1-10: LGTM! Clean plugin registration.

The rule is properly imported and registered in the plugin's rules map following the existing pattern.

packages/eslint-config/src/index.js (2)

103-109: LGTM! Rule correctly applied to module files.

The rule is appropriately configured for **/*.module.ts files to enforce the restriction on exporting repositories.


110-115: LGTM! Rule correctly applied to service and repository files.

The rule is appropriately configured for service and repository files to prevent cross-module repository usage.

packages/eslint-config/src/index.spec.ts (2)

274-286: LGTM! Test expectations correctly updated.

The test properly validates that the new rule is included in the ESLint configuration for both module and service/repository files.


307-319: LGTM! Consistent test coverage for error scenarios.

The test correctly validates the rule configuration when the host project cannot be determined.

packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/README.md (1)

1-78: LGTM! Comprehensive documentation with clear examples.

The README provides excellent documentation with:

  • Clear rule objectives
  • Illustrative incorrect/correct examples
  • Repository detection heuristics
  • Module boundary detection explanation
packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts (1)

131-155: Verify repository detection patterns in packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts

Automated scan returned no matches for the rule's current patterns (no files matched '.repo(sitory)?.ts' and no class names contained 'repo'). Confirm your project's repository file/class naming and export patterns, or broaden the rule to detect actual patterns used (e.g. files named *Repository.ts, classes suffixed "Repository", index re-exports, repository directories).

Comment on lines 131 to 155
ImportDeclaration(node) {
const importPath = node.source.value;
if (isRepositoryFile(importPath) || importPath.includes("repository")) {
node.specifiers.forEach((spec) => {
if (
spec.type === AST_NODE_TYPES.ImportSpecifier &&
spec.imported.type === AST_NODE_TYPES.Identifier &&
isRepositoryClass(spec.imported.name)
) {
repositoryImports.set(spec.local.name, importPath);

if (!isModuleFile) {
context.report({
node: spec,
messageId: "crossModuleRepositoryImport",
data: {
repositoryName: spec.imported.name,
importPath,
},
});
}
}
});
}
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Module boundary detection needs improvement.

The current implementation has critical issues with module boundary detection:

  1. The isModuleFile flag is set globally but never reset, causing all files processed after a module file to be incorrectly treated as module files.
  2. The rule assumes files are processed in a specific order, which is not guaranteed.
  3. The rule doesn't actually determine which repositories belong to which module based on the providers array as documented.

The module boundary detection logic needs to be redesigned. Instead of using a stateful isModuleFile flag, consider:

  1. Analyzing the AST to find the @Module decorator and its providers array
  2. Building a map of which repositories belong to which module
  3. Using file path analysis to determine module boundaries more reliably

Here's a conceptual approach:

- let isModuleFile = false;
+ const moduleProviders = new Set<string>();
+ const hasModuleDecorator = (node: TSESTree.Program): boolean => {
+   // Traverse the AST to find @Module decorator
+   // Return true if found
+ };

  return {
+   Program(node) {
+     // Determine if this file is a module file at the start
+     if (hasModuleDecorator(node)) {
+       // Extract providers from @Module decorator
+       // Store them in moduleProviders
+     }
+   },
    Decorator(node) {
      if (
        node.expression.type === AST_NODE_TYPES.CallExpression &&
        node.expression.callee.type === AST_NODE_TYPES.Identifier
      ) {
        if (node.expression.callee.name === "Module") {
-         isModuleFile = true;
          const moduleConfig = node.expression.arguments[0];
          if (moduleConfig?.type === AST_NODE_TYPES.ObjectExpression) {
            checkModuleExports(moduleConfig, context);
          }
        }

Committable suggestion skipped: line range outside the PR's diff.


🛠️ Refactor suggestion

Import path analysis may produce false positives.

The condition importPath.includes("repository") on line 133 is too broad and might flag legitimate imports from paths that happen to contain "repository" but aren't actually repository imports.

Apply this diff to make the detection more precise:

  ImportDeclaration(node) {
    const importPath = node.source.value;
-   if (isRepositoryFile(importPath) || importPath.includes("repository")) {
+   if (isRepositoryFile(importPath)) {
      node.specifiers.forEach((spec) => {
        if (
          spec.type === AST_NODE_TYPES.ImportSpecifier &&
          spec.imported.type === AST_NODE_TYPES.Identifier &&
          isRepositoryClass(spec.imported.name)
        ) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ImportDeclaration(node) {
const importPath = node.source.value;
if (isRepositoryFile(importPath) || importPath.includes("repository")) {
node.specifiers.forEach((spec) => {
if (
spec.type === AST_NODE_TYPES.ImportSpecifier &&
spec.imported.type === AST_NODE_TYPES.Identifier &&
isRepositoryClass(spec.imported.name)
) {
repositoryImports.set(spec.local.name, importPath);
if (!isModuleFile) {
context.report({
node: spec,
messageId: "crossModuleRepositoryImport",
data: {
repositoryName: spec.imported.name,
importPath,
},
});
}
}
});
}
},
ImportDeclaration(node) {
const importPath = node.source.value;
if (isRepositoryFile(importPath)) {
node.specifiers.forEach((spec) => {
if (
spec.type === AST_NODE_TYPES.ImportSpecifier &&
spec.imported.type === AST_NODE_TYPES.Identifier &&
isRepositoryClass(spec.imported.name)
) {
repositoryImports.set(spec.local.name, importPath);
if (!isModuleFile) {
context.report({
node: spec,
messageId: "crossModuleRepositoryImport",
data: {
repositoryName: spec.imported.name,
importPath,
},
});
}
}
});
}
},
🤖 Prompt for AI Agents
In
packages/eslint-plugin/src/lib/rules/no-cross-module-repository-usage/index.ts
around lines 131-155, the current importPath.includes("repository") check is too
broad and causes false positives; replace it with a precise
path-segment/filename check: parse importPath into segments (using path.posix or
path.sep-aware split) and match only when a segment equals "repository" or
"repositories" or when the basename (without extension) ends with "repository"
(use a regex like /(^|\/)(repository|repositories)(\/|$)/ or check
path.basename(importPath).replace(ext, '').endsWith('repository')); also ensure
package names (no path separators) are ignored or treated separately. Update
imports at top (require/import path if needed) and use the refined condition in
place of importPath.includes("repository") to reduce false positives.

Comment on lines 157 to 167
MethodDefinition(node) {
if (
node.kind === "constructor" &&
node.value.type === AST_NODE_TYPES.FunctionExpression &&
!isModuleFile
) {
node.value.params.forEach((parameter_) => {
checkConstructorParameter(parameter_, repositoryImports, context);
});
}
},
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Constructor parameter checking doesn't handle all injection patterns.

The current implementation only checks TSParameterProperty types, missing regular parameters with decorators.

  MethodDefinition(node) {
    if (
      node.kind === "constructor" &&
      node.value.type === AST_NODE_TYPES.FunctionExpression &&
      !isModuleFile
    ) {
      node.value.params.forEach((parameter_) => {
-       checkConstructorParameter(parameter_, repositoryImports, context);
+       // Check both TSParameterProperty and regular parameters with decorators
+       if (parameter_.type === AST_NODE_TYPES.TSParameterProperty) {
+         checkConstructorParameter(parameter_, repositoryImports, context);
+       } else if (parameter_.type === AST_NODE_TYPES.Identifier && parameter_.decorators) {
+         // Check for @Inject decorators on regular parameters
+         parameter_.decorators.forEach((decorator) => {
+           if (
+             decorator.expression.type === AST_NODE_TYPES.CallExpression &&
+             decorator.expression.callee.type === AST_NODE_TYPES.Identifier &&
+             decorator.expression.callee.name === "Inject"
+           ) {
+             checkInjectDecorator(decorator, repositoryImports, context);
+           }
+         });
+       }
      });
    }
  }

Committable suggestion skipped: line range outside the PR's diff.

…es errors

- Replace [] with never[] in RuleContext generic type parameters
- Maintain proper TypeScript typing while satisfying ESLint ban-types rule
- All tests continue to pass with proper type safety

Co-Authored-By: [email protected] <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

1 participant