Skip to content

feat: add support for biome for linting/formatting#172

Open
BradenM wants to merge 11 commits intomainfrom
feat/biome-support
Open

feat: add support for biome for linting/formatting#172
BradenM wants to merge 11 commits intomainfrom
feat/biome-support

Conversation

@BradenM
Copy link
Copy Markdown
Member

@BradenM BradenM commented Apr 22, 2026

  • feat(projen.component.linting): extract linting backend, refactor eslint backend, implement biome backend
  • feat(projen.project.typescript): support biome in ts linting builders, hooks builder
  • feat(projen.project.nx-monorepo): add nx target for biome if any subproject has it
  • feat(projen.component.linting): make backend protected, not private
  • feat(projen.component.linting): use source project linting backend when linting files
  • feat(projenrc): use correct lint cmd in lint staged

Summary by CodeRabbit

  • New Features

    • Add selectable lint/format backend (ESLint or Biome) per project; commands, lint-staged, and formatting requests follow the chosen backend.
    • Nx monorepos get caching defaults for Biome targets when subprojects opt in.
    • Builders now support Biome options and choose appropriate lint-staged commands.
  • Bug Fixes

    • Emit scoped root Biome config for opted-in subprojects and optionally remove Biome from test tasks.
    • Vue lint setup safely skips when ESLint config is missing.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

Add a pluggable lint backend abstraction (eslint | biome), implement EslintBackend and BiomeBackend, propagate backend selection through TypeScript builders, nx-monorepo, and lint-staged flows, and adjust config emission and task wiring for Biome-enabled projects.

Changes

Cohort / File(s) Summary
Linting component
packages/projen/component/linting/src/linting.ts
Introduce LintBackendKind and LintBackend interface; add EslintBackend and BiomeBackend; extend LintConfigOptions with backend, biomeOptions, removeBiomeFromTestTask; delegate format/ignore/import/task/exec behavior to backend; add compatibility shims for eslint APIs; Biome backend removes existing eslint/prettier artifacts, emits root+leaf biome.jsonc config, ensures includes, injects TS testdir overrides, and optionally removes Biome from test tasks.
TypeScript builders & hooks
packages/projen/project/typescript/src/builders.ts
TypescriptLintingBuilder captures biome/biomeOptions in applyOptions, disables native wiring when Biome enabled, and creates LintConfig with backend: 'biome'; TypescriptLintStagedHooksBuilder adds biomeCmd option and selects lint-staged commands based on project backend.
Nx monorepo integration
packages/projen/project/nx-monorepo/src/nx-monorepo.ts
applyNx() defers extra nx.json changes to pre-synth: scans subprojects for Biome usage and, if any, adds nx.targetDefaults.biome with caching inputs including default, root biome.jsonc, and an externalDependencies: ['@biomejs/biome'] entry.
Monorepo root lint-staged
projenrc/monorepo.ts
Make TS lint-staged command selection backend-driven per subproject: use biome check ... --write for Biome backend, otherwise keep the previous ESLint-based command; pass chosen command into LintStaged.
Vue integration
packages/projen/component/vue/src/vue.ts
Guard applyLintConfig on both component.eslint and component.eslintFile and return early if either is missing to avoid operating on undefined ESLint fields.

Sequence Diagram(s)

sequenceDiagram
  participant Dev as Developer
  participant Project as Project/Builders
  participant LintConfig as LintConfig
  participant Backend as LintBackend (Eslint / Biome)
  participant Tasks as Tasks / nx / lint-staged

  Dev->>Project: configure project with lint backend option
  Project->>LintConfig: LintConfig.of(project) / instantiate with backend
  LintConfig->>Backend: initialize selected backend
  Backend->>LintConfig: provide exec/format command, task-updater, pre-synth hooks
  LintConfig->>Tasks: update lint task steps / emit backend-specific config files
  Tasks->>Backend: run lint/format during lint-staged, test, nx runs
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete relative to the template requirement. The template specifies 'Fixes #' (indicating linked issues), but the provided description uses bullet points listing feature commits without addressing the template structure or linking any issues. Add a 'Fixes #' section linking to the related issue(s) to comply with the repository's pull request description template.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change—adding Biome support for linting/formatting—which aligns with the extensive backend abstraction work across multiple files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/biome-support

Review rate limit: 4/5 reviews remaining, refill in 12 minutes.

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

@nx-cloud
Copy link
Copy Markdown

nx-cloud Bot commented Apr 22, 2026

View your CI Pipeline Execution ↗ for commit 1abd59d

Command Status Duration Result
nx affected --target=build --output-style=strea... ✅ Succeeded 2m 42s View ↗

☁️ Nx Cloud last updated this comment at 2026-04-30 19:28:34 UTC

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
packages/projen/component/linting/src/linting.ts (1)

50-62: ⚠️ Potential issue | 🔴 Critical

FormatRequest leaks a non-exported type into the public API.

Line 62 exposes LintBackend from an exported interface, but LintBackend is not exported (abstract class defined at line 80). This breaks TypeScript declaration emit and external callers cannot legally provide the linter field. Move linter to an internal request type, or export a public LintBackend interface instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/component/linting/src/linting.ts` around lines 50 - 62, The
exported interface FormatRequest currently references a non-exported type
LintBackend which leaks an internal type into the public API; update the code so
the public API is valid by either (A) making LintBackend public (export the
abstract class or expose a public interface named LintBackend) and keeping the
linter?: LintBackend field on FormatRequest, or (B) remove the linter field from
the exported FormatRequest and create an internal request type (e.g.,
InternalFormatRequest or FormatRequestInternal) that includes linter?:
LintBackend for internal use; locate the declarations of FormatRequest and
LintBackend in linting.ts and apply one of these two changes so TypeScript
declaration emit no longer references an unexported type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/projen/component/linting/src/linting.ts`:
- Around line 212-228: The current logic builds extendsPath and always sets
biomeConfig.extends to [extendsPath], which causes a project to extend itself
when findRootProject(project) === project; update the code that builds
leafOptions (used to construct Biome in new Biome(project, leafOptions)) to only
add extendsPath into biomeConfig.extends when findRootProject(project) !==
project (or when rootRelative !== '.'), otherwise leave extends empty or omit
the extends property; also consider coordinating with maybeEmitRootBiomeConfig
to ensure a separate root config is only referenced when it will actually be
emitted.

In `@packages/projen/project/typescript/src/builders.ts`:
- Around line 293-301: The YAML pre-commit hook in applyLintStaged
unconditionally runs "prettier --write" causing failures when
lintConfig?.backend === 'biome' and prettier is disabled; update the YAML entry
to follow the TS/JS pattern (check lintConfig?.backend === 'biome') and
conditionally use the biome formatter command (or skip adding a prettier
command) when the backend is 'biome' so the YAML hook either runs the biome
formatting command or is omitted, mirroring the conditional logic used in the
TS/JS handlers inside applyLintStaged.

---

Outside diff comments:
In `@packages/projen/component/linting/src/linting.ts`:
- Around line 50-62: The exported interface FormatRequest currently references a
non-exported type LintBackend which leaks an internal type into the public API;
update the code so the public API is valid by either (A) making LintBackend
public (export the abstract class or expose a public interface named
LintBackend) and keeping the linter?: LintBackend field on FormatRequest, or (B)
remove the linter field from the exported FormatRequest and create an internal
request type (e.g., InternalFormatRequest or FormatRequestInternal) that
includes linter?: LintBackend for internal use; locate the declarations of
FormatRequest and LintBackend in linting.ts and apply one of these two changes
so TypeScript declaration emit no longer references an unexported type.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 785dd8e5-8030-4d2c-b866-ca636c56d291

📥 Commits

Reviewing files that changed from the base of the PR and between b00f74f and 9553e96.

📒 Files selected for processing (4)
  • packages/projen/component/linting/src/linting.ts
  • packages/projen/project/nx-monorepo/src/nx-monorepo.ts
  • packages/projen/project/typescript/src/builders.ts
  • projenrc/monorepo.ts

Comment thread packages/projen/component/linting/src/linting.ts
Comment thread packages/projen/project/typescript/src/builders.ts
@BradenM BradenM force-pushed the feat/biome-support branch from 9553e96 to c2b77dd Compare April 23, 2026 22:45
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
packages/projen/component/linting/src/linting.ts (1)

212-225: ⚠️ Potential issue | 🔴 Critical

Don't make a root biome.jsonc extend itself.

When findRootProject(project) === project, this builds extends: ['./biome.jsonc'] with root: false. That makes standalone Biome projects—and monorepo roots that opt into Biome directly—generate a self-referential config. maybeEmitRootBiomeConfig() does not fix that case because it only emits a separate root config for projects with children.

This needs a root-project branch that omits extends and marks the config as root instead.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/component/linting/src/linting.ts` around lines 212 - 225, The
code currently sets extendsPath and builds leafOptions.biomeConfig with extends
even when findRootProject(project) === project, causing self-referential
configs; update the branch where root equals project to instead omit the extends
property and set biomeConfig.root = true (e.g., when root === project, construct
leafOptions.biomeConfig without extends and with root: true), otherwise keep the
existing extendsPath and root: false behavior; touch the logic that builds
leafOptions (and any use in maybeEmitRootBiomeConfig) to ensure root projects do
not include extends ['./biome.jsonc'] or root: false.
packages/projen/project/typescript/src/builders.ts (1)

386-393: ⚠️ Potential issue | 🟠 Major

Don't invoke Prettier unconditionally in biome mode.

TypescriptLintingBuilder.applyOptions() turns prettier off when biome is enabled, but this YAML entry still always emits prettier --write. In a Biome-backed project, lint-staged can now call a binary that is no longer installed.

Possible fix
-				{
-					extensions: [ExtensionMatch.YAML],
-					commands: [
-						NodePackageUtils.command.exec(
-							project.package.packageManager,
-							this.prettierCmd,
-						),
-					],
-				},
+				...(lintConfig?.backend === 'biome'
+					? []
+					: [
+							{
+								extensions: [ExtensionMatch.YAML],
+								commands: [
+									NodePackageUtils.command.exec(
+										project.package.packageManager,
+										this.prettierCmd,
+									),
+								],
+							},
+						]),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/project/typescript/src/builders.ts` around lines 386 - 393,
The YAML lint-staged entry currently always emits the Prettier command
(this.prettierCmd) even when Biome is enabled; update the code that builds the
YAML commands (the block producing the extensions/commands array) to include the
NodePackageUtils.command.exec(project.package.packageManager, this.prettierCmd)
entry only when Prettier is actually enabled (use the same condition/flag used
by TypescriptLintingBuilder.applyOptions(), e.g., check this.prettierCmd or the
builder's prettier-enabled flag) so that Biome-backed projects do not reference
a missing Prettier binary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/projen/component/linting/src/linting.ts`:
- Around line 212-225: The code currently sets extendsPath and builds
leafOptions.biomeConfig with extends even when findRootProject(project) ===
project, causing self-referential configs; update the branch where root equals
project to instead omit the extends property and set biomeConfig.root = true
(e.g., when root === project, construct leafOptions.biomeConfig without extends
and with root: true), otherwise keep the existing extendsPath and root: false
behavior; touch the logic that builds leafOptions (and any use in
maybeEmitRootBiomeConfig) to ensure root projects do not include extends
['./biome.jsonc'] or root: false.

In `@packages/projen/project/typescript/src/builders.ts`:
- Around line 386-393: The YAML lint-staged entry currently always emits the
Prettier command (this.prettierCmd) even when Biome is enabled; update the code
that builds the YAML commands (the block producing the extensions/commands
array) to include the
NodePackageUtils.command.exec(project.package.packageManager, this.prettierCmd)
entry only when Prettier is actually enabled (use the same condition/flag used
by TypescriptLintingBuilder.applyOptions(), e.g., check this.prettierCmd or the
builder's prettier-enabled flag) so that Biome-backed projects do not reference
a missing Prettier binary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a7f670c8-bb9d-405c-b763-29781172a031

📥 Commits

Reviewing files that changed from the base of the PR and between 9553e96 and c2b77dd.

📒 Files selected for processing (4)
  • packages/projen/component/linting/src/linting.ts
  • packages/projen/project/nx-monorepo/src/nx-monorepo.ts
  • packages/projen/project/typescript/src/builders.ts
  • projenrc/monorepo.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • projenrc/monorepo.ts

@BradenM BradenM force-pushed the feat/biome-support branch from 283636f to fd22b45 Compare April 24, 2026 20:26
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/projen/component/linting/src/linting.ts (2)

488-500: Type coercions bypass Biome config validation.

The as never casts work around projen's Biome config typing, but suppress compile-time validation. If the Biome config schema changes upstream, these won't catch type errors.

Consider using string literal types or the actual enum values if projen exposes them.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/component/linting/src/linting.ts` around lines 488 - 500, The
Biome config is using "as never" casts (see biomeConfig -> formatter.indentStyle
and javascript.formatter.quoteStyle/semicolons in linting.ts), which bypasses
compile-time validation; replace these casts with the correct literal types or
the exported enum/union types from projen/biome (import the appropriate enum or
type and use e.g. the enum value or a string literal that matches the declared
type) so the TypeScript compiler enforces the Biome schema instead of silencing
errors with "as never".

187-191: Consider defensive checks for task existence.

The non-null assertions assume the eslint task exists with an executable step. While this should hold when EslintBackend is properly instantiated, a guard would prevent cryptic runtime errors if the task is missing or modified.

♻️ Suggested defensive check
 setLintExec(exec: string, replace: string = 'eslint'): void {
-   const task = this.project.tasks.tryFind('eslint')!
-   const cmd = task.steps[0].exec!.replace(replace, exec)
-   replaceTask(this.project, 'eslint', [{ exec: cmd }])
+   const task = this.project.tasks.tryFind('eslint')
+   const existingExec = task?.steps[0]?.exec
+   if (!existingExec) {
+       this.project.logger.warn('eslint task not found or missing exec step')
+       return
+   }
+   const cmd = existingExec.replace(replace, exec)
+   replaceTask(this.project, 'eslint', [{ exec: cmd }])
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/component/linting/src/linting.ts` around lines 187 - 191, The
method setLintExec uses non-null assertions on the result of
this.project.tasks.tryFind('eslint') and on task.steps[0].exec, which can
produce cryptic runtime errors if the eslint task or its steps are missing; add
defensive checks in setLintExec to verify the task exists and that task.steps
and task.steps[0].exec are defined before proceeding (e.g., if (!task ||
!task.steps?.[0]?.exec) throw or return a clear error), then safely compute cmd
and call replaceTask(this.project, 'eslint', [{ exec: cmd }]); ensure you remove
the non-null assertions and surface a descriptive message mentioning setLintExec
and the eslint task when failing.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/projen/component/linting/src/linting.ts`:
- Around line 488-500: The Biome config is using "as never" casts (see
biomeConfig -> formatter.indentStyle and
javascript.formatter.quoteStyle/semicolons in linting.ts), which bypasses
compile-time validation; replace these casts with the correct literal types or
the exported enum/union types from projen/biome (import the appropriate enum or
type and use e.g. the enum value or a string literal that matches the declared
type) so the TypeScript compiler enforces the Biome schema instead of silencing
errors with "as never".
- Around line 187-191: The method setLintExec uses non-null assertions on the
result of this.project.tasks.tryFind('eslint') and on task.steps[0].exec, which
can produce cryptic runtime errors if the eslint task or its steps are missing;
add defensive checks in setLintExec to verify the task exists and that
task.steps and task.steps[0].exec are defined before proceeding (e.g., if (!task
|| !task.steps?.[0]?.exec) throw or return a clear error), then safely compute
cmd and call replaceTask(this.project, 'eslint', [{ exec: cmd }]); ensure you
remove the non-null assertions and surface a descriptive message mentioning
setLintExec and the eslint task when failing.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6e12052e-3435-4912-aad3-a42adea9db21

📥 Commits

Reviewing files that changed from the base of the PR and between 283636f and fd22b45.

📒 Files selected for processing (5)
  • packages/projen/component/linting/src/linting.ts
  • packages/projen/component/vue/src/vue.ts
  • packages/projen/project/nx-monorepo/src/nx-monorepo.ts
  • packages/projen/project/typescript/src/builders.ts
  • projenrc/monorepo.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/projen/component/vue/src/vue.ts
  • projenrc/monorepo.ts

@BradenM BradenM force-pushed the feat/biome-support branch from fd22b45 to cf285dc Compare April 30, 2026 15:35
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
projenrc/monorepo.ts (1)

84-106: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make the YAML lint-staged command backend-aware too.

This block switches TS/JS to Biome, but the YAML entry still hard-codes prettier --write. For subprojects created with TypescriptLintingBuilder in Biome mode, Prettier is explicitly turned off, so a staged YAML file will still break pre-commit. This needs the same useBiome ? biomeCmd : prettierCmd split that already exists in packages/projen/project/typescript/src/builders.ts.

Suggested fix
 					const lintConfig = LintConfig.of(project)
+					const useBiome = lintConfig?.backend === 'biome'
 					const tsCmd =
-						lintConfig?.backend === 'biome'
+						useBiome
 							? 'biome check --files-ignore-unknown=true --no-errors-on-unmatched --write'
 							: 'eslint --no-error-on-unmatched-pattern --fix'
+					const yamlCmd = useBiome
+						? 'biome check --files-ignore-unknown=true --no-errors-on-unmatched --write'
+						: 'prettier --write'
 					new LintStaged(project, {
 						entries: [
 							{
@@
 							{
 								extensions: [ExtensionMatch.YAML],
 								commands: [
 									NodePackageUtils.command.exec(
 										this.package.packageManager,
-										'prettier --write',
+										yamlCmd,
 									),
 								],
 							},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@projenrc/monorepo.ts` around lines 84 - 106, The YAML lint-staged entry
hard-codes "prettier --write" which breaks Biome-only projects; mirror the TS/JS
logic: obtain lintConfig via LintConfig.of(project) and compute a yamlCmd
similar to tsCmd (e.g., use the Biome formatting command when
lintConfig?.backend === 'biome', otherwise "prettier --write"), then replace the
hard-coded string in the LintStaged entries (the YAML entry under new
LintStaged(...), where
NodePackageUtils.command.exec(this.package.packageManager, 'prettier --write')
is used) with NodePackageUtils.command.exec(this.package.packageManager,
yamlCmd) so YAML uses the backend-aware formatter.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/projen/component/linting/src/linting.ts`:
- Around line 211-226: The root/standalone Biome config path (in BiomeBackend
where leafOptions and the isRoot branch are built) is missing the repo
formatting defaults (tabs, singleQuote, asNeeded) that maybeEmitRootBiomeConfig
emits, causing root/standalone projects to fall back to Biome defaults; fix by
extracting the repo formatting defaults into a shared helper (e.g.,
getRepoFormattingDefaults or reusing maybeEmitRootBiomeConfig logic) and merge
those defaults into the biomeConfig for the isRoot branch when constructing
leafOptions (referencing leafOptions, biomeConfig, isRoot, and
maybeEmitRootBiomeConfig/BiomeBackend) so both the root and the separate
emission path use the same formatting defaults.
- Around line 65-78: The shared linting component is removing Biome from every
NodeProject's testTask via removeBiomeFromTestTask(project, biome), which breaks
standalone consumers; change this to be opt-in or relocated to Nx-specific
integration: add an explicit boolean option (e.g. removeBiomeFromTestTask =
false) to the linting component's options and only call removeBiomeFromTestTask
if that option is true (or alternatively move the call into the Nx integration
code path that knows the Nx target exists); update the linting component
constructor and all call sites that create Biome to pass the option (or enable
it from the Nx integrator) so standalone NodeProject/biome users keep linting in
pnpm test.

---

Duplicate comments:
In `@projenrc/monorepo.ts`:
- Around line 84-106: The YAML lint-staged entry hard-codes "prettier --write"
which breaks Biome-only projects; mirror the TS/JS logic: obtain lintConfig via
LintConfig.of(project) and compute a yamlCmd similar to tsCmd (e.g., use the
Biome formatting command when lintConfig?.backend === 'biome', otherwise
"prettier --write"), then replace the hard-coded string in the LintStaged
entries (the YAML entry under new LintStaged(...), where
NodePackageUtils.command.exec(this.package.packageManager, 'prettier --write')
is used) with NodePackageUtils.command.exec(this.package.packageManager,
yamlCmd) so YAML uses the backend-aware formatter.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eea4179d-33c2-4fe8-8222-4f83b1cf101a

📥 Commits

Reviewing files that changed from the base of the PR and between 283636f and cf285dc.

📒 Files selected for processing (5)
  • packages/projen/component/linting/src/linting.ts
  • packages/projen/component/vue/src/vue.ts
  • packages/projen/project/nx-monorepo/src/nx-monorepo.ts
  • packages/projen/project/typescript/src/builders.ts
  • projenrc/monorepo.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/projen/project/nx-monorepo/src/nx-monorepo.ts

Comment thread packages/projen/component/linting/src/linting.ts
Comment thread packages/projen/component/linting/src/linting.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/projen/component/linting/src/linting.ts (1)

59-71: ⚠️ Potential issue | 🔴 Critical

Exported FormatRequest leaks a private type.

FormatRequest.linter is typed as LintBackend, but LintBackend is not exported. Since src/index.ts re-exports all exports from this module, FormatRequest becomes part of the public API. TypeScript declaration emit will fail because it cannot resolve the LintBackend type for external consumers. Either make FormatRequest internal or export a public formatter interface instead of the concrete backend base class.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/component/linting/src/linting.ts` around lines 59 - 71,
FormatRequest currently exposes a non-exported type via its optional linter
property (LintBackend), causing declaration emit failures; fix by replacing the
private type with a public contract or hiding the type: either export a public
interface (e.g., name it Formatter or ILintBackend) that describes the
methods/properties needed and use that public interface as the type for
FormatRequest.linter, or mark FormatRequest as internal (remove its export) so
it is not re-exported by src/index.ts; update usages of LintBackend in this
module to use the new exported interface (or adjust callers if FormatRequest is
made internal) and ensure src/index.ts no longer re-exports the private type.
♻️ Duplicate comments (2)
packages/projen/project/typescript/src/builders.ts (1)

312-318: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't force removeBiomeFromTestTask for every TypeScript Biome project.

This overrides the linting component's opt-in default for all callers, so standalone TypeScript projects lose Biome from pnpm test as soon as they switch backends. This should stay caller-controlled or be enabled only from the Nx-specific integration that provides the replacement target.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/project/typescript/src/builders.ts` around lines 312 - 318,
The code is forcing removeBiomeFromTestTask: true when constructing the
LintConfig for Biome (in builders.ts inside the TypeScript project builder),
which overrides callers' defaults; remove that hardcoded property so the flag is
caller-controlled (either omit removeBiomeFromTestTask entirely from the object
so LintConfig uses its opt-in default or pass through
this.options.removeBiomeFromTestTask). If the behavior should only be enabled
for the Nx integration, set removeBiomeFromTestTask only in the Nx-specific
integration code (not here) when creating the LintConfig for Nx projects.
packages/projen/component/linting/src/linting.ts (1)

520-545: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Root biome emission still strips Biome from pnpm test unconditionally.

maybeEmitRootBiomeConfig() always calls removeBiomeFromTestTask() for the generated root Biome task. That bypasses the new opt-in flag and means a monorepo root stops running Biome in pnpm test as soon as any child opts into the Biome backend.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/component/linting/src/linting.ts` around lines 520 - 545,
maybeEmitRootBiomeConfig currently always calls
removeBiomeFromTestTask(rootBiomeProject, rootBiome) which unconditionally
strips Biome from the root pnpm test; change this by guarding that call with the
repo/monorepo opt-in flag (the same flag used to decide whether to emit/enable
root Biome for children) so removeBiomeFromTestTask is only invoked when the
opt-in behavior requires removing root Biome from test runs. Locate
maybeEmitRootBiomeConfig and wrap the removeBiomeFromTestTask(...) call in a
conditional that checks the existing opt-in property/config used for child Biome
opting, or add a boolean check exposed by the same configuration path, so the
root Biome is left in pnpm test unless opt-in dictates removal.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/projen/project/typescript/src/builders.ts`:
- Around line 280-304: The builder currently only checks typed.biome in
applyOptions so specifying backend: 'biome' via applyProject
(this.options.backend) doesn't disable Projen's native lint stack; update
applyOptions to consider both typed.biome and options.backend === 'biome' when
setting this.useBiome and this.biomeOptions, and ensure the returned options
object (when enabling Biome) always sets biome: false, eslint: false, prettier:
false to prevent Projen wiring; also review applyProject to ensure it doesn't
override this.useBiome or this.biomeOptions set here.

---

Outside diff comments:
In `@packages/projen/component/linting/src/linting.ts`:
- Around line 59-71: FormatRequest currently exposes a non-exported type via its
optional linter property (LintBackend), causing declaration emit failures; fix
by replacing the private type with a public contract or hiding the type: either
export a public interface (e.g., name it Formatter or ILintBackend) that
describes the methods/properties needed and use that public interface as the
type for FormatRequest.linter, or mark FormatRequest as internal (remove its
export) so it is not re-exported by src/index.ts; update usages of LintBackend
in this module to use the new exported interface (or adjust callers if
FormatRequest is made internal) and ensure src/index.ts no longer re-exports the
private type.

---

Duplicate comments:
In `@packages/projen/component/linting/src/linting.ts`:
- Around line 520-545: maybeEmitRootBiomeConfig currently always calls
removeBiomeFromTestTask(rootBiomeProject, rootBiome) which unconditionally
strips Biome from the root pnpm test; change this by guarding that call with the
repo/monorepo opt-in flag (the same flag used to decide whether to emit/enable
root Biome for children) so removeBiomeFromTestTask is only invoked when the
opt-in behavior requires removing root Biome from test runs. Locate
maybeEmitRootBiomeConfig and wrap the removeBiomeFromTestTask(...) call in a
conditional that checks the existing opt-in property/config used for child Biome
opting, or add a boolean check exposed by the same configuration path, so the
root Biome is left in pnpm test unless opt-in dictates removal.

In `@packages/projen/project/typescript/src/builders.ts`:
- Around line 312-318: The code is forcing removeBiomeFromTestTask: true when
constructing the LintConfig for Biome (in builders.ts inside the TypeScript
project builder), which overrides callers' defaults; remove that hardcoded
property so the flag is caller-controlled (either omit removeBiomeFromTestTask
entirely from the object so LintConfig uses its opt-in default or pass through
this.options.removeBiomeFromTestTask). If the behavior should only be enabled
for the Nx integration, set removeBiomeFromTestTask only in the Nx-specific
integration code (not here) when creating the LintConfig for Nx projects.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 62ecc697-d351-4b92-be71-14d81813b7b6

📥 Commits

Reviewing files that changed from the base of the PR and between cf285dc and 1abd59d.

📒 Files selected for processing (2)
  • packages/projen/component/linting/src/linting.ts
  • packages/projen/project/typescript/src/builders.ts

Comment on lines +280 to +304
applyOptions(
options: ProjectOptions & this['_outputOptions'],
): ProjectOptions & this['_outputOptions'] {
const typed = options as ProjectOptions &
this['_outputOptions'] & {
biome?: boolean
biomeOptions?: javascript.BiomeOptions
eslint?: boolean
prettier?: boolean
}
// Always reset instance state — this builder is shared across projects.
this.useBiome = !!typed.biome
this.biomeOptions = typed.biomeOptions
if (this.useBiome) {
// Take over linting: skip Projen's native biome/eslint/prettier wiring
// so LintConfig can manage a single tool end-to-end.
return {
...options,
biome: false,
eslint: false,
prettier: false,
}
}
return options
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Builder-level backend: 'biome' never disables the native lint stack.

applyOptions() only keys off typed.biome, but applyProject() also accepts this.options.backend === 'biome'. A caller using new TypescriptLintingBuilder({ backend: 'biome' }) will get a Biome LintConfig without turning off Projen's native eslint/prettier wiring, so both stacks can be configured at once.

Proposed fix
-		this.useBiome = !!typed.biome
-		this.biomeOptions = typed.biomeOptions
+		this.useBiome = !!typed.biome || this.options?.backend === 'biome'
+		this.biomeOptions = typed.biomeOptions ?? this.options?.biomeOptions
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/projen/project/typescript/src/builders.ts` around lines 280 - 304,
The builder currently only checks typed.biome in applyOptions so specifying
backend: 'biome' via applyProject (this.options.backend) doesn't disable
Projen's native lint stack; update applyOptions to consider both typed.biome and
options.backend === 'biome' when setting this.useBiome and this.biomeOptions,
and ensure the returned options object (when enabling Biome) always sets biome:
false, eslint: false, prettier: false to prevent Projen wiring; also review
applyProject to ensure it doesn't override this.useBiome or this.biomeOptions
set here.

Sign up for free to 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.

1 participant