fix(lint): detect imported process env usage#10470
Conversation
🦋 Changeset detectedLatest commit: 7efde0d The changes in this PR will be included in the next version bump. This PR includes changesets to release 13 packages
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 |
✅ Organic activityNo automation signals detected in the analyzed events. This is an automated analysis by AgentScan |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe PR extends the noProcessEnv lint to detect environment reads when env is imported as a named export from "process" or "node:process". The rule resolves static member expressions, checks object === process and property === env, and uses is_env_from_process (ancestor-based) to confirm the binding originates from a named Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
crates/biome_js_analyze/src/lint/style/no_process_env.rs (1)
103-114: ⚡ Quick winAvoid allocating and rescanning ancestors in
is_env_from_process.This path runs per candidate member access; collecting
ancestorsinto aVecadds avoidable overhead.♻️ Proposed refactor
fn is_env_from_process(binding: &biome_js_semantic::Binding) -> bool { const PROCESS_MODULE_NAMES: [&str; 2] = ["process", "node:process"]; - let ancestors: Vec<_> = binding.syntax().ancestors().collect(); - ancestors - .iter() - .find_map(|n| AnyJsNamedImportSpecifier::cast(n.clone())?.imported_name()) - .is_some_and(|name| name.text_trimmed() == "env") - && ancestors - .iter() - .find_map(|n| JsImport::cast(n.clone())?.source_text().ok()) - .is_some_and(|src| PROCESS_MODULE_NAMES.contains(&src.text())) + let imports_env = binding + .syntax() + .ancestors() + .find_map(|n| AnyJsNamedImportSpecifier::cast(n)?.imported_name()) + .is_some_and(|name| name.text_trimmed() == "env"); + + let from_process_module = binding + .syntax() + .ancestors() + .find_map(|n| JsImport::cast(n)?.source_text().ok()) + .is_some_and(|src| PROCESS_MODULE_NAMES.contains(&src.text())); + + imports_env && from_process_module }🤖 Prompt for 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. In `@crates/biome_js_analyze/src/lint/style/no_process_env.rs` around lines 103 - 114, The function is_env_from_process currently collects binding.syntax().ancestors() into a Vec and rescans it twice; change it to a single non-allocating pass over binding.syntax().ancestors() (iterate the iterator directly) and track two booleans (found_env and found_process_src) as you inspect each ancestor: if AnyJsNamedImportSpecifier::cast yields an imported_name equal to "env" set found_env, and if JsImport::cast yields a source_text matching "process" or "node:process" set found_process_src; return found_env && found_process_src as the result. This removes the Vec allocation and dual scans while keeping the logic in is_env_from_process.crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnv.js (1)
1-3: ⚡ Quick winAdd a direct
node:processnamed-import invalid fixture as well.Nice start. You currently cover direct import for
processand aliased import fornode:process; adding a non-aliasedimport { env } from 'node:process'fixture would lock the full behaviour matrix.🤖 Prompt for 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. In `@crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnv.js` around lines 1 - 3, Add a parallel invalid fixture that uses a named import from the node:process specifier; specifically create/update a test that does "import { env } from 'node:process'" and then references env.NODE_ENV and env.HOME so the runner covers the non-aliased node:process named-import case (symbols: env and the import statement "import { env } from 'node:process'"). Ensure this new fixture mirrors the existing invalidImportedEnv.js usage pattern so the full behaviour matrix is tested.
🤖 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 @.changeset/no-process-env-named-import.md:
- Line 5: Update the changeset .changeset/no-process-env-named-import.md to
include the issue reference and rule link: prepend the summary with "Fixed
[`#10447`](https://github.com/biomejs/biome/issues/10447):" and include a link to
the rule using the exact symbol
[noProcessEnv](https://biomejs.dev/linter/rules/no-process-env/); also make the
description explicit about what previously wasn't detected (uses of env imported
as a named export or aliased import, e.g. import { env } from 'node:process' and
import { env as processEnv } from 'process') so it clearly states the gap that
was fixed.
---
Nitpick comments:
In `@crates/biome_js_analyze/src/lint/style/no_process_env.rs`:
- Around line 103-114: The function is_env_from_process currently collects
binding.syntax().ancestors() into a Vec and rescans it twice; change it to a
single non-allocating pass over binding.syntax().ancestors() (iterate the
iterator directly) and track two booleans (found_env and found_process_src) as
you inspect each ancestor: if AnyJsNamedImportSpecifier::cast yields an
imported_name equal to "env" set found_env, and if JsImport::cast yields a
source_text matching "process" or "node:process" set found_process_src; return
found_env && found_process_src as the result. This removes the Vec allocation
and dual scans while keeping the logic in is_env_from_process.
In
`@crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnv.js`:
- Around line 1-3: Add a parallel invalid fixture that uses a named import from
the node:process specifier; specifically create/update a test that does "import
{ env } from 'node:process'" and then references env.NODE_ENV and env.HOME so
the runner covers the non-aliased node:process named-import case (symbols: env
and the import statement "import { env } from 'node:process'"). Ensure this new
fixture mirrors the existing invalidImportedEnv.js usage pattern so the full
behaviour matrix is tested.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 24bce479-a2aa-4b01-bc20-ee5e50db4e17
⛔ Files ignored due to path filters (3)
crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnv.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnvAlias.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/noProcessEnv/validImportedEnv.js.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (5)
.changeset/no-process-env-named-import.mdcrates/biome_js_analyze/src/lint/style/no_process_env.rscrates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnv.jscrates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnvAlias.jscrates/biome_js_analyze/tests/specs/style/noProcessEnv/validImportedEnv.js
446729e to
93dc7f9
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
crates/biome_js_analyze/src/lint/style/no_process_env.rs (1)
103-114: 💤 Low valueOptional: avoid intermediate Vec allocation.
The ancestors can be iterated twice directly rather than collecting into a
Vecfirst. Minor optimisation, feel free to defer.♻️ Suggested change
fn is_env_from_process(binding: &biome_js_semantic::Binding) -> bool { const PROCESS_MODULE_NAMES: [&str; 2] = ["process", "node:process"]; - let ancestors: Vec<_> = binding.syntax().ancestors().collect(); - ancestors + binding + .syntax() + .ancestors() - .iter() - .find_map(|n| AnyJsNamedImportSpecifier::cast(n.clone())?.imported_name()) + .find_map(|n| AnyJsNamedImportSpecifier::cast(n)?.imported_name()) .is_some_and(|name| name.text_trimmed() == "env") - && ancestors + && binding + .syntax() + .ancestors() - .iter() - .find_map(|n| JsImport::cast(n.clone())?.source_text().ok()) + .find_map(|n| JsImport::cast(n)?.source_text().ok()) .is_some_and(|src| PROCESS_MODULE_NAMES.contains(&src.text())) }🤖 Prompt for 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. In `@crates/biome_js_analyze/src/lint/style/no_process_env.rs` around lines 103 - 114, The code in is_env_from_process allocates a Vec by collecting binding.syntax().ancestors() but only needs to iterate ancestors twice; remove the intermediate Vec and call binding.syntax().ancestors() directly for each check instead (i.e. replace ancestors.iter().find_map(...) and the second ancestors.iter().find_map(...) with two separate calls to binding.syntax().ancestors().find_map(...)). Keep the same casts to AnyJsNamedImportSpecifier and JsImport and the same checks (imported_name() == "env" and source_text() in PROCESS_MODULE_NAMES) but invoke binding.syntax().ancestors() twice to avoid the temporary allocation.
🤖 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.
Nitpick comments:
In `@crates/biome_js_analyze/src/lint/style/no_process_env.rs`:
- Around line 103-114: The code in is_env_from_process allocates a Vec by
collecting binding.syntax().ancestors() but only needs to iterate ancestors
twice; remove the intermediate Vec and call binding.syntax().ancestors()
directly for each check instead (i.e. replace ancestors.iter().find_map(...) and
the second ancestors.iter().find_map(...) with two separate calls to
binding.syntax().ancestors().find_map(...)). Keep the same casts to
AnyJsNamedImportSpecifier and JsImport and the same checks (imported_name() ==
"env" and source_text() in PROCESS_MODULE_NAMES) but invoke
binding.syntax().ancestors() twice to avoid the temporary allocation.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f4a78188-1b3f-44ad-895f-1052fd62a183
⛔ Files ignored due to path filters (3)
crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnv.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnvAlias.js.snapis excluded by!**/*.snapand included by**crates/biome_js_analyze/tests/specs/style/noProcessEnv/validImportedEnv.js.snapis excluded by!**/*.snapand included by**
📒 Files selected for processing (5)
.changeset/no-process-env-named-import.mdcrates/biome_js_analyze/src/lint/style/no_process_env.rscrates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnv.jscrates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnvAlias.jscrates/biome_js_analyze/tests/specs/style/noProcessEnv/validImportedEnv.js
✅ Files skipped from review due to trivial changes (3)
- crates/biome_js_analyze/tests/specs/style/noProcessEnv/validImportedEnv.js
- crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnv.js
- .changeset/no-process-env-named-import.md
🚧 Files skipped from review as they are similar to previous changes (1)
- crates/biome_js_analyze/tests/specs/style/noProcessEnv/invalidImportedEnvAlias.js
| } | ||
|
|
||
| fn is_env_from_process(binding: &biome_js_semantic::Binding) -> bool { | ||
| const PROCESS_MODULE_NAMES: [&str; 2] = ["process", "node:process"]; |
There was a problem hiding this comment.
It's a const, you can move it outside of the function
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
|
Still need to add some cases because the new changes don't cover |
Merging this PR will improve performance by 11.16%
Performance Changes
Tip Curious why this is faster? Comment Comparing Footnotes
|
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ematipico
left a comment
There was a problem hiding this comment.
The latest commit wasn't needed. The change was way simpler. Use AnyJsImportLike instead of JsImport. The comment was more for me, I didn't know were still maintaining the PR
✅ Organic activityNo automation signals detected in the analyzed events. This is an automated analysis by AgentScan |
|
Leave it to me. I'll fix it. |
✅ Organic activityNo automation signals detected in the analyzed events. This is an automated analysis by AgentScan |
Summary
Fixes #10447.
noProcessEnv already reports direct process.env usage, but it was missing cases where env is imported from process or node:process.
This updates the rule to also catch cases like:
while still ignoring unrelated local imports with the same name.
I used AI tools while investigating the issue and tracing the relevant code paths, but reviewed and cleaned up the final implementation and tests manually before submitting.
Test Plan
Added fixtures/snapshots covering:
Ran the relevant noProcessEnv analyzer tests and regenerated snapshots with the normal test workflow.
Docs
No documentation changes needed.