Skip to content

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule - #8704

Merged
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule
Jun 17, 2026
Merged

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule#8704
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule

Conversation

@Ephem

@EphemEphem commented May 29, 2026

Copy link
Copy Markdown
Member

Description

This PR also contains this one that was merged into it and implements suggestions and fixing-capabilities: #8828

Adds @clerk/eslint-plugin, a package for eslint-rules. Adds a first rule, require-auth-protection that enforces auth protections for the Next app router at the page/route/server action level.

The rule flags any page, layout, template, default, route, or Server Action under folders configured as protected that doesn't guard itself with await auth.protect() (or an equivalent early-exit auth() check).

What's included

  • New package packages/eslint-plugin (dual CJS/ESM, type-only deps, eslint >=9 peer, ships at 0.0.00.1.0)
  • require-auth-protection rule with protected (required), public, and mixedScopeLayouts options
  • Full test suite
  • CI setup

Config

importclerkNextPluginfrom'@clerk/eslint-plugin/next';exportdefault[{plugins: {'@clerk/next': clerkNextPlugin},rules: {'@clerk/next/require-auth-protection': ['error',{protected: ['app/**'],public: ['app/sign-in/**','app/sign-up/**'],},],},},];

Also see README for more options. It is possible to be extra strict by providing explicit mixedScopeLayouts, and it's possible to turn off checking for routeHandlers, serverFunctions or serverComponentEntrypoints.

Errors

  • missingProtect:
    • 'Expected await auth.protect() at the top of {{subject}} in a folder configured as protected. Add the call to the top of the function, move the file into a public folder, or configure this folder as public.',
  • exportImported:
    • "This {{subject}} is exported from '{{source}}'. The rule cannot follow imports across files. Add a wrapper with await auth.protect(), or ensure the imported function calls it and add an eslint-disable comment with a reason.",
  • unverifiableExport:
    • 'This {{subject}} could not be verified as being protected, likely because it is assigned from a call expression (e.g. const handler = withAuth(impl)). Inline a function literal that calls await auth.protect(), or add an eslint-disable comment with a reason.',
  • unlistedMixedScopeLayout (only if an explicit mixedScopeLayouts was provided in config):
    • "This {{fileKind}} at '{{folder}}/' wraps both protected and public descendants but is not listed in mixedScopeLayouts. Either add '{{folder}}' to the list to acknowledge the mixed scope, or restructure so the {{fileKind}} wraps only public or protected descendants.",

Notes

  • Verified against our internal dashboard repo
  • Other tooling is upcoming
  • ✅ Before merging and releasing, we need to double check first time publish via OIDC will work
    • @clerk/eslint-plugin package has been published and set up for trusted publishing, so should work
    • pkg-pr-new does not work since current npm pkg does not have the correct setup, will work after first publish

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Added @clerk/eslint-plugin-next with an experimental require-auth-protection rule for Next.js App Router to enforce auth guards on pages, routes, layouts, and server functions.
  • Documentation

    • Added README describing installation, configuration options, recognized auth checks, and usage examples.
  • Tests

    • Comprehensive test suites covering folder classification, pattern matching, protection detection, rule behavior, and schema validation.
  • Chores

    • Package metadata, build/test configs, license, and CI labeler updates for publishing.

@changeset-bot

changeset-botBot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd47e6c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/eslint-pluginMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 17, 2026 8:44pm
swingsetReadyReadyPreview, CommentJun 17, 2026 8:44pm

Request Review

@coderabbitai

coderabbitaiBot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new package @clerk/eslint-plugin-next implementing an ESLint plugin with a single rule require-auth-protection that classifies App Router folders (protected/public) via globs, resolves exported handlers, and enforces top-of-function auth guards with comprehensive tests and packaging/build configs.

Changes

Auth Protection ESLint Plugin

Layer / File(s)Summary
Package Configuration and Build Setup
packages/eslint-plugin-next/package.json, .changeset/eslint-plugin-next-initial.md, .github/labeler.yml, packages/eslint-plugin-next/tsconfig.json, packages/eslint-plugin-next/tsdown.config.mts, packages/eslint-plugin-next/vitest.config.mts, packages/eslint-plugin-next/vitest.setup.mts, packages/eslint-plugin-next/src/global.d.ts, packages/eslint-plugin-next/LICENSE
npm package manifest and exports wiring (ESM/CJS + types), changelog changeset, GitHub labeler entry, TypeScript config, tsdown build config, Vitest setup and config, a test-time PACKAGE_VERSION global, and MIT license.
File Kind Classification and Module Directives
packages/eslint-plugin-next/src/lib/file-info.ts, packages/eslint-plugin-next/src/__tests__/file-info.test.ts
Utilities to normalize paths to the first app segment, derive Next.js resource kinds (page/layout/template/default/route), and detect use server/use client directives. Tests validate path/cwd/edge-case behaviors.
Glob Pattern Matching and Folder Classification
packages/eslint-plugin-next/src/lib/match-folders.ts, packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
Glob matcher supporting literal segments, * (single segment) and ** (multi-segment), specificity scoring, literal-prefix extraction, descendant detection, and classification into protected/public/unmatched, with tests exercising wildcard combos and tie-breaking.
Export Resolution from AST
packages/eslint-plugin-next/src/lib/exports.ts
AST helpers and types to unwrap function nodes, resolve local identifiers to function/import targets, resolve default exports, and iterate named/export-all declarations while skipping type-only exports.
Auth Protection Detection at Function Entry
packages/eslint-plugin-next/src/lib/protection-checks.ts
Detection of local auth import names, recognition of auth.protect() (direct or awaited) and captured-destructure + guard patterns, exit-action recognition (redirect, notFound, etc.), and hasProtectAtTop() for async functions with non-runtime statement skipping.
ESLint Plugin Entry and Rule Implementation
packages/eslint-plugin-next/src/index.ts, packages/eslint-plugin-next/src/rules/require-auth-protection.ts
Plugin registration exporting a typed ESLint plugin, rule option schema (required protected globs), folder classification, export-target verification for default/named/export * handlers, inline server-function scanning, and message reporting for missing/unverifiable/imported exports and mixed-scope layouts.
Require Auth Protection Rule Behavior Tests
packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
Extensive RuleTester-based valid and invalid matrices covering correct/incorrect protection patterns, export-resolution edge cases, mixed-scope layout behavior, inline server functions, intercepting routes, and schema validation for rule options.
User Documentation
packages/eslint-plugin-next/README.md
README describing plugin purpose, installation (ESLint >= 9 flat config), configuration example, rule options and glob semantics, recognized auth-check patterns, client skipping behavior, and contributing/security/license notes.

Sequence Diagram(s)

sequenceDiagram
participant ESLint
participant RequireAuthRule
participant FileInfo
participant MatchFolders
participant Exports
participant ProtectionChecks
ESLint->>RequireAuthRule: visit program node
RequireAuthRule->>FileInfo: getRelativeFolder, getFileKind, isClientModule
RequireAuthRule->>MatchFolders: classifyFolder
alt Protected Folder
RequireAuthRule->>Exports: resolveDefaultExportTarget or iterateNamedExports
Exports-->>RequireAuthRule: export target (function or imported)
RequireAuthRule->>ProtectionChecks: hasProtectAtTop, findAuthLocalNames
ProtectionChecks-->>RequireAuthRule: boolean protection status
end
RequireAuthRule-->>ESLint: report violation or pass
Loading
sequenceDiagram
participant Rule
participant ProtectionChecks
participant FunctionNode
Rule->>ProtectionChecks: hasProtectAtTop(fn, authNames)
ProtectionChecks->>FunctionNode: find first executable statement
alt Top-level auth.protect() call
FunctionNode-->>ProtectionChecks: returns true
else await auth() destructuring + guard
ProtectionChecks->>FunctionNode: extract captured auth fields
ProtectionChecks->>FunctionNode: recognize auth-check condition
ProtectionChecks->>FunctionNode: verify guard consequent exits
FunctionNode-->>ProtectionChecks: returns true if exits via return/throw/redirect
else No recognized pattern
FunctionNode-->>ProtectionChecks: returns false
end
ProtectionChecks-->>Rule: boolean protection status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through folders, globs in paw,
AST nibbles caught what guards might miss,
Pages, routes, and server calls I saw,
A tidy rule to keep auth bliss. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 5.41% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe PR title clearly and specifically identifies the main change: adding an initial ESLint plugin package (@clerk/eslint-plugin-next) with its first rule.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/eslint-plugin-next/README.md`:
- Line 5: The <img> tag in the README is missing an alt attribute; add an
appropriate alt attribute to the image element (e.g., alt="Clerk logo" or a more
descriptive string) so screen readers can convey the image content—if the image
is decorative, use alt="" to mark it as decorative; update the <img
src="https://images.clerk.com/static/logo-light-mode-400x400.png" height="64">
element accordingly.
In `@packages/eslint-plugin-next/src/lib/protection-checks.ts`:
- Around line 96-133: The current logic in capturedAuthBindings wrongly treats
multi-declarator statements like `const {userId} = await auth(), side =
doWork()` as safe; update the guard to require a single declarator by checking
that stmt.declarations.length === 1 and returning null if not, so only
statements with exactly one declarator (the destructuring await) are considered;
keep the existing checks (decl.id/ObjectPattern, decl.init/AwaitExpression, arg
CallExpression, callee in authNames, and the AUTH_FIELDS/property identity
checks) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7ac258ec-bf2d-4658-bdc3-ee5333e132e6

📥 Commits

Reviewing files that changed from the base of the PR and between 1c42351 and 63c2aa4.

⛔ Files ignored due to path filters (2)
  • packages/eslint-plugin-next/src/__tests__/__snapshots__/plugin-shape.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/eslint-plugin-next-initial.md
  • .github/labeler.yml
  • packages/eslint-plugin-next/README.md
  • packages/eslint-plugin-next/package.json
  • packages/eslint-plugin-next/src/__tests__/file-info.test.ts
  • packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
  • packages/eslint-plugin-next/src/__tests__/plugin-shape.test.ts
  • packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
  • packages/eslint-plugin-next/src/global.d.ts
  • packages/eslint-plugin-next/src/index.ts
  • packages/eslint-plugin-next/src/lib/exports.ts
  • packages/eslint-plugin-next/src/lib/file-info.ts
  • packages/eslint-plugin-next/src/lib/match-folders.ts
  • packages/eslint-plugin-next/src/lib/protection-checks.ts
  • packages/eslint-plugin-next/src/rules/require-auth-protection.ts
  • packages/eslint-plugin-next/tsconfig.json
  • packages/eslint-plugin-next/tsup.config.ts
  • packages/eslint-plugin-next/vitest.config.mts
  • packages/eslint-plugin-next/vitest.setup.mts

Comment threadpackages/eslint-plugin/README.md
Comment threadpackages/eslint-plugin-next/src/lib/protection-checks.ts Outdated
@EphemEphem changed the title Add initial @clerk/eslint-plugin-next package and rulefeat(eslint-plugin-next): Add initial @clerk/eslint-plugin-next package and ruleMay 29, 2026
Comment threadpackages/eslint-plugin-next/README.md Outdated
…require-auth-protection rule (#8828)
Co-authored-by: Jacek Radko <jacek@clerk.dev>

@jacekradkojacekradko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@Ephem
Ephem enabled auto-merge (squash) June 17, 2026 20:46
@Ephem
Ephem merged commit 8184111 into mainJun 17, 2026
73 of 76 checks passed
@Ephem
Ephem deleted the fredrik/add-experimental-next-lint-rule branch June 17, 2026 20:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Ephem@jacekradko@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule by Ephem · Pull Request #8704 · clerk/javascript · GitHub
Skip to content

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule - #8704

Merged
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule
Jun 17, 2026
Merged

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule#8704
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule

Conversation

@Ephem

@EphemEphem commented May 29, 2026

Copy link
Copy Markdown
Member

Description

This PR also contains this one that was merged into it and implements suggestions and fixing-capabilities: #8828

Adds @clerk/eslint-plugin, a package for eslint-rules. Adds a first rule, require-auth-protection that enforces auth protections for the Next app router at the page/route/server action level.

The rule flags any page, layout, template, default, route, or Server Action under folders configured as protected that doesn't guard itself with await auth.protect() (or an equivalent early-exit auth() check).

What's included

  • New package packages/eslint-plugin (dual CJS/ESM, type-only deps, eslint >=9 peer, ships at 0.0.00.1.0)
  • require-auth-protection rule with protected (required), public, and mixedScopeLayouts options
  • Full test suite
  • CI setup

Config

importclerkNextPluginfrom'@clerk/eslint-plugin/next';exportdefault[{plugins: {'@clerk/next': clerkNextPlugin},rules: {'@clerk/next/require-auth-protection': ['error',{protected: ['app/**'],public: ['app/sign-in/**','app/sign-up/**'],},],},},];

Also see README for more options. It is possible to be extra strict by providing explicit mixedScopeLayouts, and it's possible to turn off checking for routeHandlers, serverFunctions or serverComponentEntrypoints.

Errors

  • missingProtect:
    • 'Expected await auth.protect() at the top of {{subject}} in a folder configured as protected. Add the call to the top of the function, move the file into a public folder, or configure this folder as public.',
  • exportImported:
    • "This {{subject}} is exported from '{{source}}'. The rule cannot follow imports across files. Add a wrapper with await auth.protect(), or ensure the imported function calls it and add an eslint-disable comment with a reason.",
  • unverifiableExport:
    • 'This {{subject}} could not be verified as being protected, likely because it is assigned from a call expression (e.g. const handler = withAuth(impl)). Inline a function literal that calls await auth.protect(), or add an eslint-disable comment with a reason.',
  • unlistedMixedScopeLayout (only if an explicit mixedScopeLayouts was provided in config):
    • "This {{fileKind}} at '{{folder}}/' wraps both protected and public descendants but is not listed in mixedScopeLayouts. Either add '{{folder}}' to the list to acknowledge the mixed scope, or restructure so the {{fileKind}} wraps only public or protected descendants.",

Notes

  • Verified against our internal dashboard repo
  • Other tooling is upcoming
  • ✅ Before merging and releasing, we need to double check first time publish via OIDC will work
    • @clerk/eslint-plugin package has been published and set up for trusted publishing, so should work
    • pkg-pr-new does not work since current npm pkg does not have the correct setup, will work after first publish

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Added @clerk/eslint-plugin-next with an experimental require-auth-protection rule for Next.js App Router to enforce auth guards on pages, routes, layouts, and server functions.
  • Documentation

    • Added README describing installation, configuration options, recognized auth checks, and usage examples.
  • Tests

    • Comprehensive test suites covering folder classification, pattern matching, protection detection, rule behavior, and schema validation.
  • Chores

    • Package metadata, build/test configs, license, and CI labeler updates for publishing.

@changeset-bot

changeset-botBot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd47e6c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/eslint-pluginMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 17, 2026 8:44pm
swingsetReadyReadyPreview, CommentJun 17, 2026 8:44pm

Request Review

@coderabbitai

coderabbitaiBot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new package @clerk/eslint-plugin-next implementing an ESLint plugin with a single rule require-auth-protection that classifies App Router folders (protected/public) via globs, resolves exported handlers, and enforces top-of-function auth guards with comprehensive tests and packaging/build configs.

Changes

Auth Protection ESLint Plugin

Layer / File(s)Summary
Package Configuration and Build Setup
packages/eslint-plugin-next/package.json, .changeset/eslint-plugin-next-initial.md, .github/labeler.yml, packages/eslint-plugin-next/tsconfig.json, packages/eslint-plugin-next/tsdown.config.mts, packages/eslint-plugin-next/vitest.config.mts, packages/eslint-plugin-next/vitest.setup.mts, packages/eslint-plugin-next/src/global.d.ts, packages/eslint-plugin-next/LICENSE
npm package manifest and exports wiring (ESM/CJS + types), changelog changeset, GitHub labeler entry, TypeScript config, tsdown build config, Vitest setup and config, a test-time PACKAGE_VERSION global, and MIT license.
File Kind Classification and Module Directives
packages/eslint-plugin-next/src/lib/file-info.ts, packages/eslint-plugin-next/src/__tests__/file-info.test.ts
Utilities to normalize paths to the first app segment, derive Next.js resource kinds (page/layout/template/default/route), and detect use server/use client directives. Tests validate path/cwd/edge-case behaviors.
Glob Pattern Matching and Folder Classification
packages/eslint-plugin-next/src/lib/match-folders.ts, packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
Glob matcher supporting literal segments, * (single segment) and ** (multi-segment), specificity scoring, literal-prefix extraction, descendant detection, and classification into protected/public/unmatched, with tests exercising wildcard combos and tie-breaking.
Export Resolution from AST
packages/eslint-plugin-next/src/lib/exports.ts
AST helpers and types to unwrap function nodes, resolve local identifiers to function/import targets, resolve default exports, and iterate named/export-all declarations while skipping type-only exports.
Auth Protection Detection at Function Entry
packages/eslint-plugin-next/src/lib/protection-checks.ts
Detection of local auth import names, recognition of auth.protect() (direct or awaited) and captured-destructure + guard patterns, exit-action recognition (redirect, notFound, etc.), and hasProtectAtTop() for async functions with non-runtime statement skipping.
ESLint Plugin Entry and Rule Implementation
packages/eslint-plugin-next/src/index.ts, packages/eslint-plugin-next/src/rules/require-auth-protection.ts
Plugin registration exporting a typed ESLint plugin, rule option schema (required protected globs), folder classification, export-target verification for default/named/export * handlers, inline server-function scanning, and message reporting for missing/unverifiable/imported exports and mixed-scope layouts.
Require Auth Protection Rule Behavior Tests
packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
Extensive RuleTester-based valid and invalid matrices covering correct/incorrect protection patterns, export-resolution edge cases, mixed-scope layout behavior, inline server functions, intercepting routes, and schema validation for rule options.
User Documentation
packages/eslint-plugin-next/README.md
README describing plugin purpose, installation (ESLint >= 9 flat config), configuration example, rule options and glob semantics, recognized auth-check patterns, client skipping behavior, and contributing/security/license notes.

Sequence Diagram(s)

sequenceDiagram
participant ESLint
participant RequireAuthRule
participant FileInfo
participant MatchFolders
participant Exports
participant ProtectionChecks
ESLint->>RequireAuthRule: visit program node
RequireAuthRule->>FileInfo: getRelativeFolder, getFileKind, isClientModule
RequireAuthRule->>MatchFolders: classifyFolder
alt Protected Folder
RequireAuthRule->>Exports: resolveDefaultExportTarget or iterateNamedExports
Exports-->>RequireAuthRule: export target (function or imported)
RequireAuthRule->>ProtectionChecks: hasProtectAtTop, findAuthLocalNames
ProtectionChecks-->>RequireAuthRule: boolean protection status
end
RequireAuthRule-->>ESLint: report violation or pass
Loading
sequenceDiagram
participant Rule
participant ProtectionChecks
participant FunctionNode
Rule->>ProtectionChecks: hasProtectAtTop(fn, authNames)
ProtectionChecks->>FunctionNode: find first executable statement
alt Top-level auth.protect() call
FunctionNode-->>ProtectionChecks: returns true
else await auth() destructuring + guard
ProtectionChecks->>FunctionNode: extract captured auth fields
ProtectionChecks->>FunctionNode: recognize auth-check condition
ProtectionChecks->>FunctionNode: verify guard consequent exits
FunctionNode-->>ProtectionChecks: returns true if exits via return/throw/redirect
else No recognized pattern
FunctionNode-->>ProtectionChecks: returns false
end
ProtectionChecks-->>Rule: boolean protection status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through folders, globs in paw,
AST nibbles caught what guards might miss,
Pages, routes, and server calls I saw,
A tidy rule to keep auth bliss. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 5.41% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe PR title clearly and specifically identifies the main change: adding an initial ESLint plugin package (@clerk/eslint-plugin-next) with its first rule.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/eslint-plugin-next/README.md`:
- Line 5: The <img> tag in the README is missing an alt attribute; add an
appropriate alt attribute to the image element (e.g., alt="Clerk logo" or a more
descriptive string) so screen readers can convey the image content—if the image
is decorative, use alt="" to mark it as decorative; update the <img
src="https://images.clerk.com/static/logo-light-mode-400x400.png" height="64">
element accordingly.
In `@packages/eslint-plugin-next/src/lib/protection-checks.ts`:
- Around line 96-133: The current logic in capturedAuthBindings wrongly treats
multi-declarator statements like `const {userId} = await auth(), side =
doWork()` as safe; update the guard to require a single declarator by checking
that stmt.declarations.length === 1 and returning null if not, so only
statements with exactly one declarator (the destructuring await) are considered;
keep the existing checks (decl.id/ObjectPattern, decl.init/AwaitExpression, arg
CallExpression, callee in authNames, and the AUTH_FIELDS/property identity
checks) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7ac258ec-bf2d-4658-bdc3-ee5333e132e6

📥 Commits

Reviewing files that changed from the base of the PR and between 1c42351 and 63c2aa4.

⛔ Files ignored due to path filters (2)
  • packages/eslint-plugin-next/src/__tests__/__snapshots__/plugin-shape.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/eslint-plugin-next-initial.md
  • .github/labeler.yml
  • packages/eslint-plugin-next/README.md
  • packages/eslint-plugin-next/package.json
  • packages/eslint-plugin-next/src/__tests__/file-info.test.ts
  • packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
  • packages/eslint-plugin-next/src/__tests__/plugin-shape.test.ts
  • packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
  • packages/eslint-plugin-next/src/global.d.ts
  • packages/eslint-plugin-next/src/index.ts
  • packages/eslint-plugin-next/src/lib/exports.ts
  • packages/eslint-plugin-next/src/lib/file-info.ts
  • packages/eslint-plugin-next/src/lib/match-folders.ts
  • packages/eslint-plugin-next/src/lib/protection-checks.ts
  • packages/eslint-plugin-next/src/rules/require-auth-protection.ts
  • packages/eslint-plugin-next/tsconfig.json
  • packages/eslint-plugin-next/tsup.config.ts
  • packages/eslint-plugin-next/vitest.config.mts
  • packages/eslint-plugin-next/vitest.setup.mts

Comment threadpackages/eslint-plugin/README.md
Comment threadpackages/eslint-plugin-next/src/lib/protection-checks.ts Outdated
@EphemEphem changed the title Add initial @clerk/eslint-plugin-next package and rulefeat(eslint-plugin-next): Add initial @clerk/eslint-plugin-next package and ruleMay 29, 2026
Comment threadpackages/eslint-plugin-next/README.md Outdated
…require-auth-protection rule (#8828)
Co-authored-by: Jacek Radko <jacek@clerk.dev>

@jacekradkojacekradko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@Ephem
Ephem enabled auto-merge (squash) June 17, 2026 20:46
@Ephem
Ephem merged commit 8184111 into mainJun 17, 2026
73 of 76 checks passed
@Ephem
Ephem deleted the fredrik/add-experimental-next-lint-rule branch June 17, 2026 20:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Ephem@jacekradko@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule by Ephem · Pull Request #8704 · clerk/javascript · GitHub
Skip to content

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule - #8704

Merged
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule
Jun 17, 2026
Merged

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule#8704
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule

Conversation

@Ephem

@EphemEphem commented May 29, 2026

Copy link
Copy Markdown
Member

Description

This PR also contains this one that was merged into it and implements suggestions and fixing-capabilities: #8828

Adds @clerk/eslint-plugin, a package for eslint-rules. Adds a first rule, require-auth-protection that enforces auth protections for the Next app router at the page/route/server action level.

The rule flags any page, layout, template, default, route, or Server Action under folders configured as protected that doesn't guard itself with await auth.protect() (or an equivalent early-exit auth() check).

What's included

  • New package packages/eslint-plugin (dual CJS/ESM, type-only deps, eslint >=9 peer, ships at 0.0.00.1.0)
  • require-auth-protection rule with protected (required), public, and mixedScopeLayouts options
  • Full test suite
  • CI setup

Config

importclerkNextPluginfrom'@clerk/eslint-plugin/next';exportdefault[{plugins: {'@clerk/next': clerkNextPlugin},rules: {'@clerk/next/require-auth-protection': ['error',{protected: ['app/**'],public: ['app/sign-in/**','app/sign-up/**'],},],},},];

Also see README for more options. It is possible to be extra strict by providing explicit mixedScopeLayouts, and it's possible to turn off checking for routeHandlers, serverFunctions or serverComponentEntrypoints.

Errors

  • missingProtect:
    • 'Expected await auth.protect() at the top of {{subject}} in a folder configured as protected. Add the call to the top of the function, move the file into a public folder, or configure this folder as public.',
  • exportImported:
    • "This {{subject}} is exported from '{{source}}'. The rule cannot follow imports across files. Add a wrapper with await auth.protect(), or ensure the imported function calls it and add an eslint-disable comment with a reason.",
  • unverifiableExport:
    • 'This {{subject}} could not be verified as being protected, likely because it is assigned from a call expression (e.g. const handler = withAuth(impl)). Inline a function literal that calls await auth.protect(), or add an eslint-disable comment with a reason.',
  • unlistedMixedScopeLayout (only if an explicit mixedScopeLayouts was provided in config):
    • "This {{fileKind}} at '{{folder}}/' wraps both protected and public descendants but is not listed in mixedScopeLayouts. Either add '{{folder}}' to the list to acknowledge the mixed scope, or restructure so the {{fileKind}} wraps only public or protected descendants.",

Notes

  • Verified against our internal dashboard repo
  • Other tooling is upcoming
  • ✅ Before merging and releasing, we need to double check first time publish via OIDC will work
    • @clerk/eslint-plugin package has been published and set up for trusted publishing, so should work
    • pkg-pr-new does not work since current npm pkg does not have the correct setup, will work after first publish

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Added @clerk/eslint-plugin-next with an experimental require-auth-protection rule for Next.js App Router to enforce auth guards on pages, routes, layouts, and server functions.
  • Documentation

    • Added README describing installation, configuration options, recognized auth checks, and usage examples.
  • Tests

    • Comprehensive test suites covering folder classification, pattern matching, protection detection, rule behavior, and schema validation.
  • Chores

    • Package metadata, build/test configs, license, and CI labeler updates for publishing.

@changeset-bot

changeset-botBot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd47e6c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/eslint-pluginMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 17, 2026 8:44pm
swingsetReadyReadyPreview, CommentJun 17, 2026 8:44pm

Request Review

@coderabbitai

coderabbitaiBot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new package @clerk/eslint-plugin-next implementing an ESLint plugin with a single rule require-auth-protection that classifies App Router folders (protected/public) via globs, resolves exported handlers, and enforces top-of-function auth guards with comprehensive tests and packaging/build configs.

Changes

Auth Protection ESLint Plugin

Layer / File(s)Summary
Package Configuration and Build Setup
packages/eslint-plugin-next/package.json, .changeset/eslint-plugin-next-initial.md, .github/labeler.yml, packages/eslint-plugin-next/tsconfig.json, packages/eslint-plugin-next/tsdown.config.mts, packages/eslint-plugin-next/vitest.config.mts, packages/eslint-plugin-next/vitest.setup.mts, packages/eslint-plugin-next/src/global.d.ts, packages/eslint-plugin-next/LICENSE
npm package manifest and exports wiring (ESM/CJS + types), changelog changeset, GitHub labeler entry, TypeScript config, tsdown build config, Vitest setup and config, a test-time PACKAGE_VERSION global, and MIT license.
File Kind Classification and Module Directives
packages/eslint-plugin-next/src/lib/file-info.ts, packages/eslint-plugin-next/src/__tests__/file-info.test.ts
Utilities to normalize paths to the first app segment, derive Next.js resource kinds (page/layout/template/default/route), and detect use server/use client directives. Tests validate path/cwd/edge-case behaviors.
Glob Pattern Matching and Folder Classification
packages/eslint-plugin-next/src/lib/match-folders.ts, packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
Glob matcher supporting literal segments, * (single segment) and ** (multi-segment), specificity scoring, literal-prefix extraction, descendant detection, and classification into protected/public/unmatched, with tests exercising wildcard combos and tie-breaking.
Export Resolution from AST
packages/eslint-plugin-next/src/lib/exports.ts
AST helpers and types to unwrap function nodes, resolve local identifiers to function/import targets, resolve default exports, and iterate named/export-all declarations while skipping type-only exports.
Auth Protection Detection at Function Entry
packages/eslint-plugin-next/src/lib/protection-checks.ts
Detection of local auth import names, recognition of auth.protect() (direct or awaited) and captured-destructure + guard patterns, exit-action recognition (redirect, notFound, etc.), and hasProtectAtTop() for async functions with non-runtime statement skipping.
ESLint Plugin Entry and Rule Implementation
packages/eslint-plugin-next/src/index.ts, packages/eslint-plugin-next/src/rules/require-auth-protection.ts
Plugin registration exporting a typed ESLint plugin, rule option schema (required protected globs), folder classification, export-target verification for default/named/export * handlers, inline server-function scanning, and message reporting for missing/unverifiable/imported exports and mixed-scope layouts.
Require Auth Protection Rule Behavior Tests
packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
Extensive RuleTester-based valid and invalid matrices covering correct/incorrect protection patterns, export-resolution edge cases, mixed-scope layout behavior, inline server functions, intercepting routes, and schema validation for rule options.
User Documentation
packages/eslint-plugin-next/README.md
README describing plugin purpose, installation (ESLint >= 9 flat config), configuration example, rule options and glob semantics, recognized auth-check patterns, client skipping behavior, and contributing/security/license notes.

Sequence Diagram(s)

sequenceDiagram
participant ESLint
participant RequireAuthRule
participant FileInfo
participant MatchFolders
participant Exports
participant ProtectionChecks
ESLint->>RequireAuthRule: visit program node
RequireAuthRule->>FileInfo: getRelativeFolder, getFileKind, isClientModule
RequireAuthRule->>MatchFolders: classifyFolder
alt Protected Folder
RequireAuthRule->>Exports: resolveDefaultExportTarget or iterateNamedExports
Exports-->>RequireAuthRule: export target (function or imported)
RequireAuthRule->>ProtectionChecks: hasProtectAtTop, findAuthLocalNames
ProtectionChecks-->>RequireAuthRule: boolean protection status
end
RequireAuthRule-->>ESLint: report violation or pass
Loading
sequenceDiagram
participant Rule
participant ProtectionChecks
participant FunctionNode
Rule->>ProtectionChecks: hasProtectAtTop(fn, authNames)
ProtectionChecks->>FunctionNode: find first executable statement
alt Top-level auth.protect() call
FunctionNode-->>ProtectionChecks: returns true
else await auth() destructuring + guard
ProtectionChecks->>FunctionNode: extract captured auth fields
ProtectionChecks->>FunctionNode: recognize auth-check condition
ProtectionChecks->>FunctionNode: verify guard consequent exits
FunctionNode-->>ProtectionChecks: returns true if exits via return/throw/redirect
else No recognized pattern
FunctionNode-->>ProtectionChecks: returns false
end
ProtectionChecks-->>Rule: boolean protection status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through folders, globs in paw,
AST nibbles caught what guards might miss,
Pages, routes, and server calls I saw,
A tidy rule to keep auth bliss. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 5.41% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe PR title clearly and specifically identifies the main change: adding an initial ESLint plugin package (@clerk/eslint-plugin-next) with its first rule.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/eslint-plugin-next/README.md`:
- Line 5: The <img> tag in the README is missing an alt attribute; add an
appropriate alt attribute to the image element (e.g., alt="Clerk logo" or a more
descriptive string) so screen readers can convey the image content—if the image
is decorative, use alt="" to mark it as decorative; update the <img
src="https://images.clerk.com/static/logo-light-mode-400x400.png" height="64">
element accordingly.
In `@packages/eslint-plugin-next/src/lib/protection-checks.ts`:
- Around line 96-133: The current logic in capturedAuthBindings wrongly treats
multi-declarator statements like `const {userId} = await auth(), side =
doWork()` as safe; update the guard to require a single declarator by checking
that stmt.declarations.length === 1 and returning null if not, so only
statements with exactly one declarator (the destructuring await) are considered;
keep the existing checks (decl.id/ObjectPattern, decl.init/AwaitExpression, arg
CallExpression, callee in authNames, and the AUTH_FIELDS/property identity
checks) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7ac258ec-bf2d-4658-bdc3-ee5333e132e6

📥 Commits

Reviewing files that changed from the base of the PR and between 1c42351 and 63c2aa4.

⛔ Files ignored due to path filters (2)
  • packages/eslint-plugin-next/src/__tests__/__snapshots__/plugin-shape.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/eslint-plugin-next-initial.md
  • .github/labeler.yml
  • packages/eslint-plugin-next/README.md
  • packages/eslint-plugin-next/package.json
  • packages/eslint-plugin-next/src/__tests__/file-info.test.ts
  • packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
  • packages/eslint-plugin-next/src/__tests__/plugin-shape.test.ts
  • packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
  • packages/eslint-plugin-next/src/global.d.ts
  • packages/eslint-plugin-next/src/index.ts
  • packages/eslint-plugin-next/src/lib/exports.ts
  • packages/eslint-plugin-next/src/lib/file-info.ts
  • packages/eslint-plugin-next/src/lib/match-folders.ts
  • packages/eslint-plugin-next/src/lib/protection-checks.ts
  • packages/eslint-plugin-next/src/rules/require-auth-protection.ts
  • packages/eslint-plugin-next/tsconfig.json
  • packages/eslint-plugin-next/tsup.config.ts
  • packages/eslint-plugin-next/vitest.config.mts
  • packages/eslint-plugin-next/vitest.setup.mts

Comment threadpackages/eslint-plugin/README.md
Comment threadpackages/eslint-plugin-next/src/lib/protection-checks.ts Outdated
@EphemEphem changed the title Add initial @clerk/eslint-plugin-next package and rulefeat(eslint-plugin-next): Add initial @clerk/eslint-plugin-next package and ruleMay 29, 2026
Comment threadpackages/eslint-plugin-next/README.md Outdated
…require-auth-protection rule (#8828)
Co-authored-by: Jacek Radko <jacek@clerk.dev>

@jacekradkojacekradko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@Ephem
Ephem enabled auto-merge (squash) June 17, 2026 20:46
@Ephem
Ephem merged commit 8184111 into mainJun 17, 2026
73 of 76 checks passed
@Ephem
Ephem deleted the fredrik/add-experimental-next-lint-rule branch June 17, 2026 20:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Ephem@jacekradko@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule by Ephem · Pull Request #8704 · clerk/javascript · GitHub
Skip to content

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule - #8704

Merged
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule
Jun 17, 2026
Merged

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule#8704
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule

Conversation

@Ephem

@EphemEphem commented May 29, 2026

Copy link
Copy Markdown
Member

Description

This PR also contains this one that was merged into it and implements suggestions and fixing-capabilities: #8828

Adds @clerk/eslint-plugin, a package for eslint-rules. Adds a first rule, require-auth-protection that enforces auth protections for the Next app router at the page/route/server action level.

The rule flags any page, layout, template, default, route, or Server Action under folders configured as protected that doesn't guard itself with await auth.protect() (or an equivalent early-exit auth() check).

What's included

  • New package packages/eslint-plugin (dual CJS/ESM, type-only deps, eslint >=9 peer, ships at 0.0.00.1.0)
  • require-auth-protection rule with protected (required), public, and mixedScopeLayouts options
  • Full test suite
  • CI setup

Config

importclerkNextPluginfrom'@clerk/eslint-plugin/next';exportdefault[{plugins: {'@clerk/next': clerkNextPlugin},rules: {'@clerk/next/require-auth-protection': ['error',{protected: ['app/**'],public: ['app/sign-in/**','app/sign-up/**'],},],},},];

Also see README for more options. It is possible to be extra strict by providing explicit mixedScopeLayouts, and it's possible to turn off checking for routeHandlers, serverFunctions or serverComponentEntrypoints.

Errors

  • missingProtect:
    • 'Expected await auth.protect() at the top of {{subject}} in a folder configured as protected. Add the call to the top of the function, move the file into a public folder, or configure this folder as public.',
  • exportImported:
    • "This {{subject}} is exported from '{{source}}'. The rule cannot follow imports across files. Add a wrapper with await auth.protect(), or ensure the imported function calls it and add an eslint-disable comment with a reason.",
  • unverifiableExport:
    • 'This {{subject}} could not be verified as being protected, likely because it is assigned from a call expression (e.g. const handler = withAuth(impl)). Inline a function literal that calls await auth.protect(), or add an eslint-disable comment with a reason.',
  • unlistedMixedScopeLayout (only if an explicit mixedScopeLayouts was provided in config):
    • "This {{fileKind}} at '{{folder}}/' wraps both protected and public descendants but is not listed in mixedScopeLayouts. Either add '{{folder}}' to the list to acknowledge the mixed scope, or restructure so the {{fileKind}} wraps only public or protected descendants.",

Notes

  • Verified against our internal dashboard repo
  • Other tooling is upcoming
  • ✅ Before merging and releasing, we need to double check first time publish via OIDC will work
    • @clerk/eslint-plugin package has been published and set up for trusted publishing, so should work
    • pkg-pr-new does not work since current npm pkg does not have the correct setup, will work after first publish

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Added @clerk/eslint-plugin-next with an experimental require-auth-protection rule for Next.js App Router to enforce auth guards on pages, routes, layouts, and server functions.
  • Documentation

    • Added README describing installation, configuration options, recognized auth checks, and usage examples.
  • Tests

    • Comprehensive test suites covering folder classification, pattern matching, protection detection, rule behavior, and schema validation.
  • Chores

    • Package metadata, build/test configs, license, and CI labeler updates for publishing.

@changeset-bot

changeset-botBot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd47e6c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/eslint-pluginMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 17, 2026 8:44pm
swingsetReadyReadyPreview, CommentJun 17, 2026 8:44pm

Request Review

@coderabbitai

coderabbitaiBot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new package @clerk/eslint-plugin-next implementing an ESLint plugin with a single rule require-auth-protection that classifies App Router folders (protected/public) via globs, resolves exported handlers, and enforces top-of-function auth guards with comprehensive tests and packaging/build configs.

Changes

Auth Protection ESLint Plugin

Layer / File(s)Summary
Package Configuration and Build Setup
packages/eslint-plugin-next/package.json, .changeset/eslint-plugin-next-initial.md, .github/labeler.yml, packages/eslint-plugin-next/tsconfig.json, packages/eslint-plugin-next/tsdown.config.mts, packages/eslint-plugin-next/vitest.config.mts, packages/eslint-plugin-next/vitest.setup.mts, packages/eslint-plugin-next/src/global.d.ts, packages/eslint-plugin-next/LICENSE
npm package manifest and exports wiring (ESM/CJS + types), changelog changeset, GitHub labeler entry, TypeScript config, tsdown build config, Vitest setup and config, a test-time PACKAGE_VERSION global, and MIT license.
File Kind Classification and Module Directives
packages/eslint-plugin-next/src/lib/file-info.ts, packages/eslint-plugin-next/src/__tests__/file-info.test.ts
Utilities to normalize paths to the first app segment, derive Next.js resource kinds (page/layout/template/default/route), and detect use server/use client directives. Tests validate path/cwd/edge-case behaviors.
Glob Pattern Matching and Folder Classification
packages/eslint-plugin-next/src/lib/match-folders.ts, packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
Glob matcher supporting literal segments, * (single segment) and ** (multi-segment), specificity scoring, literal-prefix extraction, descendant detection, and classification into protected/public/unmatched, with tests exercising wildcard combos and tie-breaking.
Export Resolution from AST
packages/eslint-plugin-next/src/lib/exports.ts
AST helpers and types to unwrap function nodes, resolve local identifiers to function/import targets, resolve default exports, and iterate named/export-all declarations while skipping type-only exports.
Auth Protection Detection at Function Entry
packages/eslint-plugin-next/src/lib/protection-checks.ts
Detection of local auth import names, recognition of auth.protect() (direct or awaited) and captured-destructure + guard patterns, exit-action recognition (redirect, notFound, etc.), and hasProtectAtTop() for async functions with non-runtime statement skipping.
ESLint Plugin Entry and Rule Implementation
packages/eslint-plugin-next/src/index.ts, packages/eslint-plugin-next/src/rules/require-auth-protection.ts
Plugin registration exporting a typed ESLint plugin, rule option schema (required protected globs), folder classification, export-target verification for default/named/export * handlers, inline server-function scanning, and message reporting for missing/unverifiable/imported exports and mixed-scope layouts.
Require Auth Protection Rule Behavior Tests
packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
Extensive RuleTester-based valid and invalid matrices covering correct/incorrect protection patterns, export-resolution edge cases, mixed-scope layout behavior, inline server functions, intercepting routes, and schema validation for rule options.
User Documentation
packages/eslint-plugin-next/README.md
README describing plugin purpose, installation (ESLint >= 9 flat config), configuration example, rule options and glob semantics, recognized auth-check patterns, client skipping behavior, and contributing/security/license notes.

Sequence Diagram(s)

sequenceDiagram
participant ESLint
participant RequireAuthRule
participant FileInfo
participant MatchFolders
participant Exports
participant ProtectionChecks
ESLint->>RequireAuthRule: visit program node
RequireAuthRule->>FileInfo: getRelativeFolder, getFileKind, isClientModule
RequireAuthRule->>MatchFolders: classifyFolder
alt Protected Folder
RequireAuthRule->>Exports: resolveDefaultExportTarget or iterateNamedExports
Exports-->>RequireAuthRule: export target (function or imported)
RequireAuthRule->>ProtectionChecks: hasProtectAtTop, findAuthLocalNames
ProtectionChecks-->>RequireAuthRule: boolean protection status
end
RequireAuthRule-->>ESLint: report violation or pass
Loading
sequenceDiagram
participant Rule
participant ProtectionChecks
participant FunctionNode
Rule->>ProtectionChecks: hasProtectAtTop(fn, authNames)
ProtectionChecks->>FunctionNode: find first executable statement
alt Top-level auth.protect() call
FunctionNode-->>ProtectionChecks: returns true
else await auth() destructuring + guard
ProtectionChecks->>FunctionNode: extract captured auth fields
ProtectionChecks->>FunctionNode: recognize auth-check condition
ProtectionChecks->>FunctionNode: verify guard consequent exits
FunctionNode-->>ProtectionChecks: returns true if exits via return/throw/redirect
else No recognized pattern
FunctionNode-->>ProtectionChecks: returns false
end
ProtectionChecks-->>Rule: boolean protection status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through folders, globs in paw,
AST nibbles caught what guards might miss,
Pages, routes, and server calls I saw,
A tidy rule to keep auth bliss. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 5.41% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe PR title clearly and specifically identifies the main change: adding an initial ESLint plugin package (@clerk/eslint-plugin-next) with its first rule.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/eslint-plugin-next/README.md`:
- Line 5: The <img> tag in the README is missing an alt attribute; add an
appropriate alt attribute to the image element (e.g., alt="Clerk logo" or a more
descriptive string) so screen readers can convey the image content—if the image
is decorative, use alt="" to mark it as decorative; update the <img
src="https://images.clerk.com/static/logo-light-mode-400x400.png" height="64">
element accordingly.
In `@packages/eslint-plugin-next/src/lib/protection-checks.ts`:
- Around line 96-133: The current logic in capturedAuthBindings wrongly treats
multi-declarator statements like `const {userId} = await auth(), side =
doWork()` as safe; update the guard to require a single declarator by checking
that stmt.declarations.length === 1 and returning null if not, so only
statements with exactly one declarator (the destructuring await) are considered;
keep the existing checks (decl.id/ObjectPattern, decl.init/AwaitExpression, arg
CallExpression, callee in authNames, and the AUTH_FIELDS/property identity
checks) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7ac258ec-bf2d-4658-bdc3-ee5333e132e6

📥 Commits

Reviewing files that changed from the base of the PR and between 1c42351 and 63c2aa4.

⛔ Files ignored due to path filters (2)
  • packages/eslint-plugin-next/src/__tests__/__snapshots__/plugin-shape.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/eslint-plugin-next-initial.md
  • .github/labeler.yml
  • packages/eslint-plugin-next/README.md
  • packages/eslint-plugin-next/package.json
  • packages/eslint-plugin-next/src/__tests__/file-info.test.ts
  • packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
  • packages/eslint-plugin-next/src/__tests__/plugin-shape.test.ts
  • packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
  • packages/eslint-plugin-next/src/global.d.ts
  • packages/eslint-plugin-next/src/index.ts
  • packages/eslint-plugin-next/src/lib/exports.ts
  • packages/eslint-plugin-next/src/lib/file-info.ts
  • packages/eslint-plugin-next/src/lib/match-folders.ts
  • packages/eslint-plugin-next/src/lib/protection-checks.ts
  • packages/eslint-plugin-next/src/rules/require-auth-protection.ts
  • packages/eslint-plugin-next/tsconfig.json
  • packages/eslint-plugin-next/tsup.config.ts
  • packages/eslint-plugin-next/vitest.config.mts
  • packages/eslint-plugin-next/vitest.setup.mts

Comment threadpackages/eslint-plugin/README.md
Comment threadpackages/eslint-plugin-next/src/lib/protection-checks.ts Outdated
@EphemEphem changed the title Add initial @clerk/eslint-plugin-next package and rulefeat(eslint-plugin-next): Add initial @clerk/eslint-plugin-next package and ruleMay 29, 2026
Comment threadpackages/eslint-plugin-next/README.md Outdated
…require-auth-protection rule (#8828)
Co-authored-by: Jacek Radko <jacek@clerk.dev>

@jacekradkojacekradko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@Ephem
Ephem enabled auto-merge (squash) June 17, 2026 20:46
@Ephem
Ephem merged commit 8184111 into mainJun 17, 2026
73 of 76 checks passed
@Ephem
Ephem deleted the fredrik/add-experimental-next-lint-rule branch June 17, 2026 20:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Ephem@jacekradko@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule by Ephem · Pull Request #8704 · clerk/javascript · GitHub
Skip to content

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule - #8704

Merged
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule
Jun 17, 2026
Merged

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule#8704
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule

Conversation

@Ephem

@EphemEphem commented May 29, 2026

Copy link
Copy Markdown
Member

Description

This PR also contains this one that was merged into it and implements suggestions and fixing-capabilities: #8828

Adds @clerk/eslint-plugin, a package for eslint-rules. Adds a first rule, require-auth-protection that enforces auth protections for the Next app router at the page/route/server action level.

The rule flags any page, layout, template, default, route, or Server Action under folders configured as protected that doesn't guard itself with await auth.protect() (or an equivalent early-exit auth() check).

What's included

  • New package packages/eslint-plugin (dual CJS/ESM, type-only deps, eslint >=9 peer, ships at 0.0.00.1.0)
  • require-auth-protection rule with protected (required), public, and mixedScopeLayouts options
  • Full test suite
  • CI setup

Config

importclerkNextPluginfrom'@clerk/eslint-plugin/next';exportdefault[{plugins: {'@clerk/next': clerkNextPlugin},rules: {'@clerk/next/require-auth-protection': ['error',{protected: ['app/**'],public: ['app/sign-in/**','app/sign-up/**'],},],},},];

Also see README for more options. It is possible to be extra strict by providing explicit mixedScopeLayouts, and it's possible to turn off checking for routeHandlers, serverFunctions or serverComponentEntrypoints.

Errors

  • missingProtect:
    • 'Expected await auth.protect() at the top of {{subject}} in a folder configured as protected. Add the call to the top of the function, move the file into a public folder, or configure this folder as public.',
  • exportImported:
    • "This {{subject}} is exported from '{{source}}'. The rule cannot follow imports across files. Add a wrapper with await auth.protect(), or ensure the imported function calls it and add an eslint-disable comment with a reason.",
  • unverifiableExport:
    • 'This {{subject}} could not be verified as being protected, likely because it is assigned from a call expression (e.g. const handler = withAuth(impl)). Inline a function literal that calls await auth.protect(), or add an eslint-disable comment with a reason.',
  • unlistedMixedScopeLayout (only if an explicit mixedScopeLayouts was provided in config):
    • "This {{fileKind}} at '{{folder}}/' wraps both protected and public descendants but is not listed in mixedScopeLayouts. Either add '{{folder}}' to the list to acknowledge the mixed scope, or restructure so the {{fileKind}} wraps only public or protected descendants.",

Notes

  • Verified against our internal dashboard repo
  • Other tooling is upcoming
  • ✅ Before merging and releasing, we need to double check first time publish via OIDC will work
    • @clerk/eslint-plugin package has been published and set up for trusted publishing, so should work
    • pkg-pr-new does not work since current npm pkg does not have the correct setup, will work after first publish

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Added @clerk/eslint-plugin-next with an experimental require-auth-protection rule for Next.js App Router to enforce auth guards on pages, routes, layouts, and server functions.
  • Documentation

    • Added README describing installation, configuration options, recognized auth checks, and usage examples.
  • Tests

    • Comprehensive test suites covering folder classification, pattern matching, protection detection, rule behavior, and schema validation.
  • Chores

    • Package metadata, build/test configs, license, and CI labeler updates for publishing.

@changeset-bot

changeset-botBot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd47e6c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/eslint-pluginMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 17, 2026 8:44pm
swingsetReadyReadyPreview, CommentJun 17, 2026 8:44pm

Request Review

@coderabbitai

coderabbitaiBot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new package @clerk/eslint-plugin-next implementing an ESLint plugin with a single rule require-auth-protection that classifies App Router folders (protected/public) via globs, resolves exported handlers, and enforces top-of-function auth guards with comprehensive tests and packaging/build configs.

Changes

Auth Protection ESLint Plugin

Layer / File(s)Summary
Package Configuration and Build Setup
packages/eslint-plugin-next/package.json, .changeset/eslint-plugin-next-initial.md, .github/labeler.yml, packages/eslint-plugin-next/tsconfig.json, packages/eslint-plugin-next/tsdown.config.mts, packages/eslint-plugin-next/vitest.config.mts, packages/eslint-plugin-next/vitest.setup.mts, packages/eslint-plugin-next/src/global.d.ts, packages/eslint-plugin-next/LICENSE
npm package manifest and exports wiring (ESM/CJS + types), changelog changeset, GitHub labeler entry, TypeScript config, tsdown build config, Vitest setup and config, a test-time PACKAGE_VERSION global, and MIT license.
File Kind Classification and Module Directives
packages/eslint-plugin-next/src/lib/file-info.ts, packages/eslint-plugin-next/src/__tests__/file-info.test.ts
Utilities to normalize paths to the first app segment, derive Next.js resource kinds (page/layout/template/default/route), and detect use server/use client directives. Tests validate path/cwd/edge-case behaviors.
Glob Pattern Matching and Folder Classification
packages/eslint-plugin-next/src/lib/match-folders.ts, packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
Glob matcher supporting literal segments, * (single segment) and ** (multi-segment), specificity scoring, literal-prefix extraction, descendant detection, and classification into protected/public/unmatched, with tests exercising wildcard combos and tie-breaking.
Export Resolution from AST
packages/eslint-plugin-next/src/lib/exports.ts
AST helpers and types to unwrap function nodes, resolve local identifiers to function/import targets, resolve default exports, and iterate named/export-all declarations while skipping type-only exports.
Auth Protection Detection at Function Entry
packages/eslint-plugin-next/src/lib/protection-checks.ts
Detection of local auth import names, recognition of auth.protect() (direct or awaited) and captured-destructure + guard patterns, exit-action recognition (redirect, notFound, etc.), and hasProtectAtTop() for async functions with non-runtime statement skipping.
ESLint Plugin Entry and Rule Implementation
packages/eslint-plugin-next/src/index.ts, packages/eslint-plugin-next/src/rules/require-auth-protection.ts
Plugin registration exporting a typed ESLint plugin, rule option schema (required protected globs), folder classification, export-target verification for default/named/export * handlers, inline server-function scanning, and message reporting for missing/unverifiable/imported exports and mixed-scope layouts.
Require Auth Protection Rule Behavior Tests
packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
Extensive RuleTester-based valid and invalid matrices covering correct/incorrect protection patterns, export-resolution edge cases, mixed-scope layout behavior, inline server functions, intercepting routes, and schema validation for rule options.
User Documentation
packages/eslint-plugin-next/README.md
README describing plugin purpose, installation (ESLint >= 9 flat config), configuration example, rule options and glob semantics, recognized auth-check patterns, client skipping behavior, and contributing/security/license notes.

Sequence Diagram(s)

sequenceDiagram
participant ESLint
participant RequireAuthRule
participant FileInfo
participant MatchFolders
participant Exports
participant ProtectionChecks
ESLint->>RequireAuthRule: visit program node
RequireAuthRule->>FileInfo: getRelativeFolder, getFileKind, isClientModule
RequireAuthRule->>MatchFolders: classifyFolder
alt Protected Folder
RequireAuthRule->>Exports: resolveDefaultExportTarget or iterateNamedExports
Exports-->>RequireAuthRule: export target (function or imported)
RequireAuthRule->>ProtectionChecks: hasProtectAtTop, findAuthLocalNames
ProtectionChecks-->>RequireAuthRule: boolean protection status
end
RequireAuthRule-->>ESLint: report violation or pass
Loading
sequenceDiagram
participant Rule
participant ProtectionChecks
participant FunctionNode
Rule->>ProtectionChecks: hasProtectAtTop(fn, authNames)
ProtectionChecks->>FunctionNode: find first executable statement
alt Top-level auth.protect() call
FunctionNode-->>ProtectionChecks: returns true
else await auth() destructuring + guard
ProtectionChecks->>FunctionNode: extract captured auth fields
ProtectionChecks->>FunctionNode: recognize auth-check condition
ProtectionChecks->>FunctionNode: verify guard consequent exits
FunctionNode-->>ProtectionChecks: returns true if exits via return/throw/redirect
else No recognized pattern
FunctionNode-->>ProtectionChecks: returns false
end
ProtectionChecks-->>Rule: boolean protection status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through folders, globs in paw,
AST nibbles caught what guards might miss,
Pages, routes, and server calls I saw,
A tidy rule to keep auth bliss. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 5.41% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe PR title clearly and specifically identifies the main change: adding an initial ESLint plugin package (@clerk/eslint-plugin-next) with its first rule.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/eslint-plugin-next/README.md`:
- Line 5: The <img> tag in the README is missing an alt attribute; add an
appropriate alt attribute to the image element (e.g., alt="Clerk logo" or a more
descriptive string) so screen readers can convey the image content—if the image
is decorative, use alt="" to mark it as decorative; update the <img
src="https://images.clerk.com/static/logo-light-mode-400x400.png" height="64">
element accordingly.
In `@packages/eslint-plugin-next/src/lib/protection-checks.ts`:
- Around line 96-133: The current logic in capturedAuthBindings wrongly treats
multi-declarator statements like `const {userId} = await auth(), side =
doWork()` as safe; update the guard to require a single declarator by checking
that stmt.declarations.length === 1 and returning null if not, so only
statements with exactly one declarator (the destructuring await) are considered;
keep the existing checks (decl.id/ObjectPattern, decl.init/AwaitExpression, arg
CallExpression, callee in authNames, and the AUTH_FIELDS/property identity
checks) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7ac258ec-bf2d-4658-bdc3-ee5333e132e6

📥 Commits

Reviewing files that changed from the base of the PR and between 1c42351 and 63c2aa4.

⛔ Files ignored due to path filters (2)
  • packages/eslint-plugin-next/src/__tests__/__snapshots__/plugin-shape.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/eslint-plugin-next-initial.md
  • .github/labeler.yml
  • packages/eslint-plugin-next/README.md
  • packages/eslint-plugin-next/package.json
  • packages/eslint-plugin-next/src/__tests__/file-info.test.ts
  • packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
  • packages/eslint-plugin-next/src/__tests__/plugin-shape.test.ts
  • packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
  • packages/eslint-plugin-next/src/global.d.ts
  • packages/eslint-plugin-next/src/index.ts
  • packages/eslint-plugin-next/src/lib/exports.ts
  • packages/eslint-plugin-next/src/lib/file-info.ts
  • packages/eslint-plugin-next/src/lib/match-folders.ts
  • packages/eslint-plugin-next/src/lib/protection-checks.ts
  • packages/eslint-plugin-next/src/rules/require-auth-protection.ts
  • packages/eslint-plugin-next/tsconfig.json
  • packages/eslint-plugin-next/tsup.config.ts
  • packages/eslint-plugin-next/vitest.config.mts
  • packages/eslint-plugin-next/vitest.setup.mts

Comment threadpackages/eslint-plugin/README.md
Comment threadpackages/eslint-plugin-next/src/lib/protection-checks.ts Outdated
@EphemEphem changed the title Add initial @clerk/eslint-plugin-next package and rulefeat(eslint-plugin-next): Add initial @clerk/eslint-plugin-next package and ruleMay 29, 2026
Comment threadpackages/eslint-plugin-next/README.md Outdated
…require-auth-protection rule (#8828)
Co-authored-by: Jacek Radko <jacek@clerk.dev>

@jacekradkojacekradko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@Ephem
Ephem enabled auto-merge (squash) June 17, 2026 20:46
@Ephem
Ephem merged commit 8184111 into mainJun 17, 2026
73 of 76 checks passed
@Ephem
Ephem deleted the fredrik/add-experimental-next-lint-rule branch June 17, 2026 20:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Ephem@jacekradko@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule by Ephem · Pull Request #8704 · clerk/javascript · GitHub
Skip to content

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule - #8704

Merged
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule
Jun 17, 2026
Merged

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule#8704
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule

Conversation

@Ephem

@EphemEphem commented May 29, 2026

Copy link
Copy Markdown
Member

Description

This PR also contains this one that was merged into it and implements suggestions and fixing-capabilities: #8828

Adds @clerk/eslint-plugin, a package for eslint-rules. Adds a first rule, require-auth-protection that enforces auth protections for the Next app router at the page/route/server action level.

The rule flags any page, layout, template, default, route, or Server Action under folders configured as protected that doesn't guard itself with await auth.protect() (or an equivalent early-exit auth() check).

What's included

  • New package packages/eslint-plugin (dual CJS/ESM, type-only deps, eslint >=9 peer, ships at 0.0.00.1.0)
  • require-auth-protection rule with protected (required), public, and mixedScopeLayouts options
  • Full test suite
  • CI setup

Config

importclerkNextPluginfrom'@clerk/eslint-plugin/next';exportdefault[{plugins: {'@clerk/next': clerkNextPlugin},rules: {'@clerk/next/require-auth-protection': ['error',{protected: ['app/**'],public: ['app/sign-in/**','app/sign-up/**'],},],},},];

Also see README for more options. It is possible to be extra strict by providing explicit mixedScopeLayouts, and it's possible to turn off checking for routeHandlers, serverFunctions or serverComponentEntrypoints.

Errors

  • missingProtect:
    • 'Expected await auth.protect() at the top of {{subject}} in a folder configured as protected. Add the call to the top of the function, move the file into a public folder, or configure this folder as public.',
  • exportImported:
    • "This {{subject}} is exported from '{{source}}'. The rule cannot follow imports across files. Add a wrapper with await auth.protect(), or ensure the imported function calls it and add an eslint-disable comment with a reason.",
  • unverifiableExport:
    • 'This {{subject}} could not be verified as being protected, likely because it is assigned from a call expression (e.g. const handler = withAuth(impl)). Inline a function literal that calls await auth.protect(), or add an eslint-disable comment with a reason.',
  • unlistedMixedScopeLayout (only if an explicit mixedScopeLayouts was provided in config):
    • "This {{fileKind}} at '{{folder}}/' wraps both protected and public descendants but is not listed in mixedScopeLayouts. Either add '{{folder}}' to the list to acknowledge the mixed scope, or restructure so the {{fileKind}} wraps only public or protected descendants.",

Notes

  • Verified against our internal dashboard repo
  • Other tooling is upcoming
  • ✅ Before merging and releasing, we need to double check first time publish via OIDC will work
    • @clerk/eslint-plugin package has been published and set up for trusted publishing, so should work
    • pkg-pr-new does not work since current npm pkg does not have the correct setup, will work after first publish

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Added @clerk/eslint-plugin-next with an experimental require-auth-protection rule for Next.js App Router to enforce auth guards on pages, routes, layouts, and server functions.
  • Documentation

    • Added README describing installation, configuration options, recognized auth checks, and usage examples.
  • Tests

    • Comprehensive test suites covering folder classification, pattern matching, protection detection, rule behavior, and schema validation.
  • Chores

    • Package metadata, build/test configs, license, and CI labeler updates for publishing.

@changeset-bot

changeset-botBot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd47e6c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/eslint-pluginMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 17, 2026 8:44pm
swingsetReadyReadyPreview, CommentJun 17, 2026 8:44pm

Request Review

@coderabbitai

coderabbitaiBot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new package @clerk/eslint-plugin-next implementing an ESLint plugin with a single rule require-auth-protection that classifies App Router folders (protected/public) via globs, resolves exported handlers, and enforces top-of-function auth guards with comprehensive tests and packaging/build configs.

Changes

Auth Protection ESLint Plugin

Layer / File(s)Summary
Package Configuration and Build Setup
packages/eslint-plugin-next/package.json, .changeset/eslint-plugin-next-initial.md, .github/labeler.yml, packages/eslint-plugin-next/tsconfig.json, packages/eslint-plugin-next/tsdown.config.mts, packages/eslint-plugin-next/vitest.config.mts, packages/eslint-plugin-next/vitest.setup.mts, packages/eslint-plugin-next/src/global.d.ts, packages/eslint-plugin-next/LICENSE
npm package manifest and exports wiring (ESM/CJS + types), changelog changeset, GitHub labeler entry, TypeScript config, tsdown build config, Vitest setup and config, a test-time PACKAGE_VERSION global, and MIT license.
File Kind Classification and Module Directives
packages/eslint-plugin-next/src/lib/file-info.ts, packages/eslint-plugin-next/src/__tests__/file-info.test.ts
Utilities to normalize paths to the first app segment, derive Next.js resource kinds (page/layout/template/default/route), and detect use server/use client directives. Tests validate path/cwd/edge-case behaviors.
Glob Pattern Matching and Folder Classification
packages/eslint-plugin-next/src/lib/match-folders.ts, packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
Glob matcher supporting literal segments, * (single segment) and ** (multi-segment), specificity scoring, literal-prefix extraction, descendant detection, and classification into protected/public/unmatched, with tests exercising wildcard combos and tie-breaking.
Export Resolution from AST
packages/eslint-plugin-next/src/lib/exports.ts
AST helpers and types to unwrap function nodes, resolve local identifiers to function/import targets, resolve default exports, and iterate named/export-all declarations while skipping type-only exports.
Auth Protection Detection at Function Entry
packages/eslint-plugin-next/src/lib/protection-checks.ts
Detection of local auth import names, recognition of auth.protect() (direct or awaited) and captured-destructure + guard patterns, exit-action recognition (redirect, notFound, etc.), and hasProtectAtTop() for async functions with non-runtime statement skipping.
ESLint Plugin Entry and Rule Implementation
packages/eslint-plugin-next/src/index.ts, packages/eslint-plugin-next/src/rules/require-auth-protection.ts
Plugin registration exporting a typed ESLint plugin, rule option schema (required protected globs), folder classification, export-target verification for default/named/export * handlers, inline server-function scanning, and message reporting for missing/unverifiable/imported exports and mixed-scope layouts.
Require Auth Protection Rule Behavior Tests
packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
Extensive RuleTester-based valid and invalid matrices covering correct/incorrect protection patterns, export-resolution edge cases, mixed-scope layout behavior, inline server functions, intercepting routes, and schema validation for rule options.
User Documentation
packages/eslint-plugin-next/README.md
README describing plugin purpose, installation (ESLint >= 9 flat config), configuration example, rule options and glob semantics, recognized auth-check patterns, client skipping behavior, and contributing/security/license notes.

Sequence Diagram(s)

sequenceDiagram
participant ESLint
participant RequireAuthRule
participant FileInfo
participant MatchFolders
participant Exports
participant ProtectionChecks
ESLint->>RequireAuthRule: visit program node
RequireAuthRule->>FileInfo: getRelativeFolder, getFileKind, isClientModule
RequireAuthRule->>MatchFolders: classifyFolder
alt Protected Folder
RequireAuthRule->>Exports: resolveDefaultExportTarget or iterateNamedExports
Exports-->>RequireAuthRule: export target (function or imported)
RequireAuthRule->>ProtectionChecks: hasProtectAtTop, findAuthLocalNames
ProtectionChecks-->>RequireAuthRule: boolean protection status
end
RequireAuthRule-->>ESLint: report violation or pass
Loading
sequenceDiagram
participant Rule
participant ProtectionChecks
participant FunctionNode
Rule->>ProtectionChecks: hasProtectAtTop(fn, authNames)
ProtectionChecks->>FunctionNode: find first executable statement
alt Top-level auth.protect() call
FunctionNode-->>ProtectionChecks: returns true
else await auth() destructuring + guard
ProtectionChecks->>FunctionNode: extract captured auth fields
ProtectionChecks->>FunctionNode: recognize auth-check condition
ProtectionChecks->>FunctionNode: verify guard consequent exits
FunctionNode-->>ProtectionChecks: returns true if exits via return/throw/redirect
else No recognized pattern
FunctionNode-->>ProtectionChecks: returns false
end
ProtectionChecks-->>Rule: boolean protection status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through folders, globs in paw,
AST nibbles caught what guards might miss,
Pages, routes, and server calls I saw,
A tidy rule to keep auth bliss. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 5.41% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe PR title clearly and specifically identifies the main change: adding an initial ESLint plugin package (@clerk/eslint-plugin-next) with its first rule.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/eslint-plugin-next/README.md`:
- Line 5: The <img> tag in the README is missing an alt attribute; add an
appropriate alt attribute to the image element (e.g., alt="Clerk logo" or a more
descriptive string) so screen readers can convey the image content—if the image
is decorative, use alt="" to mark it as decorative; update the <img
src="https://images.clerk.com/static/logo-light-mode-400x400.png" height="64">
element accordingly.
In `@packages/eslint-plugin-next/src/lib/protection-checks.ts`:
- Around line 96-133: The current logic in capturedAuthBindings wrongly treats
multi-declarator statements like `const {userId} = await auth(), side =
doWork()` as safe; update the guard to require a single declarator by checking
that stmt.declarations.length === 1 and returning null if not, so only
statements with exactly one declarator (the destructuring await) are considered;
keep the existing checks (decl.id/ObjectPattern, decl.init/AwaitExpression, arg
CallExpression, callee in authNames, and the AUTH_FIELDS/property identity
checks) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7ac258ec-bf2d-4658-bdc3-ee5333e132e6

📥 Commits

Reviewing files that changed from the base of the PR and between 1c42351 and 63c2aa4.

⛔ Files ignored due to path filters (2)
  • packages/eslint-plugin-next/src/__tests__/__snapshots__/plugin-shape.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/eslint-plugin-next-initial.md
  • .github/labeler.yml
  • packages/eslint-plugin-next/README.md
  • packages/eslint-plugin-next/package.json
  • packages/eslint-plugin-next/src/__tests__/file-info.test.ts
  • packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
  • packages/eslint-plugin-next/src/__tests__/plugin-shape.test.ts
  • packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
  • packages/eslint-plugin-next/src/global.d.ts
  • packages/eslint-plugin-next/src/index.ts
  • packages/eslint-plugin-next/src/lib/exports.ts
  • packages/eslint-plugin-next/src/lib/file-info.ts
  • packages/eslint-plugin-next/src/lib/match-folders.ts
  • packages/eslint-plugin-next/src/lib/protection-checks.ts
  • packages/eslint-plugin-next/src/rules/require-auth-protection.ts
  • packages/eslint-plugin-next/tsconfig.json
  • packages/eslint-plugin-next/tsup.config.ts
  • packages/eslint-plugin-next/vitest.config.mts
  • packages/eslint-plugin-next/vitest.setup.mts

Comment threadpackages/eslint-plugin/README.md
Comment threadpackages/eslint-plugin-next/src/lib/protection-checks.ts Outdated
@EphemEphem changed the title Add initial @clerk/eslint-plugin-next package and rulefeat(eslint-plugin-next): Add initial @clerk/eslint-plugin-next package and ruleMay 29, 2026
Comment threadpackages/eslint-plugin-next/README.md Outdated
…require-auth-protection rule (#8828)
Co-authored-by: Jacek Radko <jacek@clerk.dev>

@jacekradkojacekradko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@Ephem
Ephem enabled auto-merge (squash) June 17, 2026 20:46
@Ephem
Ephem merged commit 8184111 into mainJun 17, 2026
73 of 76 checks passed
@Ephem
Ephem deleted the fredrik/add-experimental-next-lint-rule branch June 17, 2026 20:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Ephem@jacekradko@wobsoriano
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule by Ephem · Pull Request #8704 · clerk/javascript · GitHub
Skip to content

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule - #8704

Merged
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule
Jun 17, 2026
Merged

feat(eslint-plugin): Add initial @clerk/eslint-plugin package and rule#8704
Ephem merged 41 commits into
mainfrom
fredrik/add-experimental-next-lint-rule

Conversation

@Ephem

@EphemEphem commented May 29, 2026

Copy link
Copy Markdown
Member

Description

This PR also contains this one that was merged into it and implements suggestions and fixing-capabilities: #8828

Adds @clerk/eslint-plugin, a package for eslint-rules. Adds a first rule, require-auth-protection that enforces auth protections for the Next app router at the page/route/server action level.

The rule flags any page, layout, template, default, route, or Server Action under folders configured as protected that doesn't guard itself with await auth.protect() (or an equivalent early-exit auth() check).

What's included

  • New package packages/eslint-plugin (dual CJS/ESM, type-only deps, eslint >=9 peer, ships at 0.0.00.1.0)
  • require-auth-protection rule with protected (required), public, and mixedScopeLayouts options
  • Full test suite
  • CI setup

Config

importclerkNextPluginfrom'@clerk/eslint-plugin/next';exportdefault[{plugins: {'@clerk/next': clerkNextPlugin},rules: {'@clerk/next/require-auth-protection': ['error',{protected: ['app/**'],public: ['app/sign-in/**','app/sign-up/**'],},],},},];

Also see README for more options. It is possible to be extra strict by providing explicit mixedScopeLayouts, and it's possible to turn off checking for routeHandlers, serverFunctions or serverComponentEntrypoints.

Errors

  • missingProtect:
    • 'Expected await auth.protect() at the top of {{subject}} in a folder configured as protected. Add the call to the top of the function, move the file into a public folder, or configure this folder as public.',
  • exportImported:
    • "This {{subject}} is exported from '{{source}}'. The rule cannot follow imports across files. Add a wrapper with await auth.protect(), or ensure the imported function calls it and add an eslint-disable comment with a reason.",
  • unverifiableExport:
    • 'This {{subject}} could not be verified as being protected, likely because it is assigned from a call expression (e.g. const handler = withAuth(impl)). Inline a function literal that calls await auth.protect(), or add an eslint-disable comment with a reason.',
  • unlistedMixedScopeLayout (only if an explicit mixedScopeLayouts was provided in config):
    • "This {{fileKind}} at '{{folder}}/' wraps both protected and public descendants but is not listed in mixedScopeLayouts. Either add '{{folder}}' to the list to acknowledge the mixed scope, or restructure so the {{fileKind}} wraps only public or protected descendants.",

Notes

  • Verified against our internal dashboard repo
  • Other tooling is upcoming
  • ✅ Before merging and releasing, we need to double check first time publish via OIDC will work
    • @clerk/eslint-plugin package has been published and set up for trusted publishing, so should work
    • pkg-pr-new does not work since current npm pkg does not have the correct setup, will work after first publish

Checklist

  • pnpm test runs as expected.
  • pnpm build runs as expected.
  • (If applicable) JSDoc comments have been added or updated for any package exports
  • (If applicable) Documentation has been updated

Type of change

  • 🐛 Bug fix
  • 🌟 New feature
  • 🔨 Breaking change
  • 📖 Refactoring / dependency upgrade / documentation
  • other:

Summary by CodeRabbit

  • New Features

    • Added @clerk/eslint-plugin-next with an experimental require-auth-protection rule for Next.js App Router to enforce auth guards on pages, routes, layouts, and server functions.
  • Documentation

    • Added README describing installation, configuration options, recognized auth checks, and usage examples.
  • Tests

    • Comprehensive test suites covering folder classification, pattern matching, protection detection, rule behavior, and schema validation.
  • Chores

    • Package metadata, build/test configs, license, and CI labeler updates for publishing.

@changeset-bot

changeset-botBot commented May 29, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: fd47e6c

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
NameType
@clerk/eslint-pluginMinor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel

vercelBot commented May 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentJun 17, 2026 8:44pm
swingsetReadyReadyPreview, CommentJun 17, 2026 8:44pm

Request Review

@coderabbitai

coderabbitaiBot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new package @clerk/eslint-plugin-next implementing an ESLint plugin with a single rule require-auth-protection that classifies App Router folders (protected/public) via globs, resolves exported handlers, and enforces top-of-function auth guards with comprehensive tests and packaging/build configs.

Changes

Auth Protection ESLint Plugin

Layer / File(s)Summary
Package Configuration and Build Setup
packages/eslint-plugin-next/package.json, .changeset/eslint-plugin-next-initial.md, .github/labeler.yml, packages/eslint-plugin-next/tsconfig.json, packages/eslint-plugin-next/tsdown.config.mts, packages/eslint-plugin-next/vitest.config.mts, packages/eslint-plugin-next/vitest.setup.mts, packages/eslint-plugin-next/src/global.d.ts, packages/eslint-plugin-next/LICENSE
npm package manifest and exports wiring (ESM/CJS + types), changelog changeset, GitHub labeler entry, TypeScript config, tsdown build config, Vitest setup and config, a test-time PACKAGE_VERSION global, and MIT license.
File Kind Classification and Module Directives
packages/eslint-plugin-next/src/lib/file-info.ts, packages/eslint-plugin-next/src/__tests__/file-info.test.ts
Utilities to normalize paths to the first app segment, derive Next.js resource kinds (page/layout/template/default/route), and detect use server/use client directives. Tests validate path/cwd/edge-case behaviors.
Glob Pattern Matching and Folder Classification
packages/eslint-plugin-next/src/lib/match-folders.ts, packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
Glob matcher supporting literal segments, * (single segment) and ** (multi-segment), specificity scoring, literal-prefix extraction, descendant detection, and classification into protected/public/unmatched, with tests exercising wildcard combos and tie-breaking.
Export Resolution from AST
packages/eslint-plugin-next/src/lib/exports.ts
AST helpers and types to unwrap function nodes, resolve local identifiers to function/import targets, resolve default exports, and iterate named/export-all declarations while skipping type-only exports.
Auth Protection Detection at Function Entry
packages/eslint-plugin-next/src/lib/protection-checks.ts
Detection of local auth import names, recognition of auth.protect() (direct or awaited) and captured-destructure + guard patterns, exit-action recognition (redirect, notFound, etc.), and hasProtectAtTop() for async functions with non-runtime statement skipping.
ESLint Plugin Entry and Rule Implementation
packages/eslint-plugin-next/src/index.ts, packages/eslint-plugin-next/src/rules/require-auth-protection.ts
Plugin registration exporting a typed ESLint plugin, rule option schema (required protected globs), folder classification, export-target verification for default/named/export * handlers, inline server-function scanning, and message reporting for missing/unverifiable/imported exports and mixed-scope layouts.
Require Auth Protection Rule Behavior Tests
packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
Extensive RuleTester-based valid and invalid matrices covering correct/incorrect protection patterns, export-resolution edge cases, mixed-scope layout behavior, inline server functions, intercepting routes, and schema validation for rule options.
User Documentation
packages/eslint-plugin-next/README.md
README describing plugin purpose, installation (ESLint >= 9 flat config), configuration example, rule options and glob semantics, recognized auth-check patterns, client skipping behavior, and contributing/security/license notes.

Sequence Diagram(s)

sequenceDiagram
participant ESLint
participant RequireAuthRule
participant FileInfo
participant MatchFolders
participant Exports
participant ProtectionChecks
ESLint->>RequireAuthRule: visit program node
RequireAuthRule->>FileInfo: getRelativeFolder, getFileKind, isClientModule
RequireAuthRule->>MatchFolders: classifyFolder
alt Protected Folder
RequireAuthRule->>Exports: resolveDefaultExportTarget or iterateNamedExports
Exports-->>RequireAuthRule: export target (function or imported)
RequireAuthRule->>ProtectionChecks: hasProtectAtTop, findAuthLocalNames
ProtectionChecks-->>RequireAuthRule: boolean protection status
end
RequireAuthRule-->>ESLint: report violation or pass
Loading
sequenceDiagram
participant Rule
participant ProtectionChecks
participant FunctionNode
Rule->>ProtectionChecks: hasProtectAtTop(fn, authNames)
ProtectionChecks->>FunctionNode: find first executable statement
alt Top-level auth.protect() call
FunctionNode-->>ProtectionChecks: returns true
else await auth() destructuring + guard
ProtectionChecks->>FunctionNode: extract captured auth fields
ProtectionChecks->>FunctionNode: recognize auth-check condition
ProtectionChecks->>FunctionNode: verify guard consequent exits
FunctionNode-->>ProtectionChecks: returns true if exits via return/throw/redirect
else No recognized pattern
FunctionNode-->>ProtectionChecks: returns false
end
ProtectionChecks-->>Rule: boolean protection status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰 I hopped through folders, globs in paw,
AST nibbles caught what guards might miss,
Pages, routes, and server calls I saw,
A tidy rule to keep auth bliss. ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 5.41% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
Title check✅ PassedThe PR title clearly and specifically identifies the main change: adding an initial ESLint plugin package (@clerk/eslint-plugin-next) with its first rule.
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/eslint-plugin-next/README.md`:
- Line 5: The <img> tag in the README is missing an alt attribute; add an
appropriate alt attribute to the image element (e.g., alt="Clerk logo" or a more
descriptive string) so screen readers can convey the image content—if the image
is decorative, use alt="" to mark it as decorative; update the <img
src="https://images.clerk.com/static/logo-light-mode-400x400.png" height="64">
element accordingly.
In `@packages/eslint-plugin-next/src/lib/protection-checks.ts`:
- Around line 96-133: The current logic in capturedAuthBindings wrongly treats
multi-declarator statements like `const {userId} = await auth(), side =
doWork()` as safe; update the guard to require a single declarator by checking
that stmt.declarations.length === 1 and returning null if not, so only
statements with exactly one declarator (the destructuring await) are considered;
keep the existing checks (decl.id/ObjectPattern, decl.init/AwaitExpression, arg
CallExpression, callee in authNames, and the AUTH_FIELDS/property identity
checks) unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 7ac258ec-bf2d-4658-bdc3-ee5333e132e6

📥 Commits

Reviewing files that changed from the base of the PR and between 1c42351 and 63c2aa4.

⛔ Files ignored due to path filters (2)
  • packages/eslint-plugin-next/src/__tests__/__snapshots__/plugin-shape.test.ts.snap is excluded by !**/*.snap
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/eslint-plugin-next-initial.md
  • .github/labeler.yml
  • packages/eslint-plugin-next/README.md
  • packages/eslint-plugin-next/package.json
  • packages/eslint-plugin-next/src/__tests__/file-info.test.ts
  • packages/eslint-plugin-next/src/__tests__/match-folders.test.ts
  • packages/eslint-plugin-next/src/__tests__/plugin-shape.test.ts
  • packages/eslint-plugin-next/src/__tests__/require-auth-protection.test.ts
  • packages/eslint-plugin-next/src/global.d.ts
  • packages/eslint-plugin-next/src/index.ts
  • packages/eslint-plugin-next/src/lib/exports.ts
  • packages/eslint-plugin-next/src/lib/file-info.ts
  • packages/eslint-plugin-next/src/lib/match-folders.ts
  • packages/eslint-plugin-next/src/lib/protection-checks.ts
  • packages/eslint-plugin-next/src/rules/require-auth-protection.ts
  • packages/eslint-plugin-next/tsconfig.json
  • packages/eslint-plugin-next/tsup.config.ts
  • packages/eslint-plugin-next/vitest.config.mts
  • packages/eslint-plugin-next/vitest.setup.mts

Comment threadpackages/eslint-plugin/README.md
Comment threadpackages/eslint-plugin-next/src/lib/protection-checks.ts Outdated
@EphemEphem changed the title Add initial @clerk/eslint-plugin-next package and rulefeat(eslint-plugin-next): Add initial @clerk/eslint-plugin-next package and ruleMay 29, 2026
Comment threadpackages/eslint-plugin-next/README.md Outdated
…require-auth-protection rule (#8828)
Co-authored-by: Jacek Radko <jacek@clerk.dev>

@jacekradkojacekradko left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

:shipit:

@Ephem
Ephem enabled auto-merge (squash) June 17, 2026 20:46
@Ephem
Ephem merged commit 8184111 into mainJun 17, 2026
73 of 76 checks passed
@Ephem
Ephem deleted the fredrik/add-experimental-next-lint-rule branch June 17, 2026 20:54
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@Ephem@jacekradko@wobsoriano