denoland/deno
Patterns and conventions
The conventions you'll encounter all over the Deno codebase. Following these matches existing code and shortens review cycles.
Copyright header
Every source file starts with:
// Copyright 2018-2026 the Deno authors. MIT license.tools/copyright_checker.js enforces this in CI. New files without the header will fail the lint job.
Rust conventions
Module organization
lib.rsis the crate root; public API surface lives there.- Sub-modules go in sibling files (
foo.rs) or sibling directories (foo/mod.rs). - The CLI in particular uses many sibling files at the top level of
cli/rather than a deeper module hierarchy. Don't fight that; new top-level CLI concerns can be a new sibling file. - Keep
mod.rsfiles small — usually justpub moddeclarations and re-exports.
Error handling
deno_core::anyhow::Error(re-exported asAnyError) is used for top-level error returns from CLI subcommands.- More structured errors use
deno_error(workspace dep) with theJsErrorBoxfamily — these are errors that get bubbled into JS land. - Lower-level crates (
libs/*,ext/*) tend to define their own crate-specific error enums withthiserror. Match the pattern of the surrounding code. Result<T, AnyError>is the workhorse return type for async CLI code paths.
Async
- The runtime uses Tokio. Async code in the CLI side runs on
current_threadruntimes (seeruntime/tokio_util.rs::create_and_run_current_thread_with_maybe_metrics). - Long futures should be
boxed_local()to avoid blowing the Windows stack in debug builds — see the comment incli/lib.rsspawn_subcommand. - Cooperative cancellation goes through
deno_core::CancelHandle/RcLikepatterns; check existing ops for the right shape.
Ops (the JS/Rust boundary)
Every privileged or runtime-level Rust function exposed to JS is an "op", written with the #[op2] attribute macro from deno_core:
#[op2(async)]
pub async fn op_fs_open_async(
state: Rc<RefCell<OpState>>,
#[string] path: String,
#[serde] options: OpenOptions,
) -> Result<ResourceId, FsError> { ... }Conventions:
- Op names:
op_<area>_<verb>(e.g.,op_fs_open_async,op_net_listen_tcp). - Sync ops drop the
_asyncsuffix. - Argument annotations (
#[string],#[serde],#[buffer],#[smi],#[bigint]) tellop2how to convert from V8 — match the convention of nearby ops. - Permission checks happen inside the op, not at the dispatch layer:
state .borrow_mut::<PermissionsContainer>() .check_read(&path, "Deno.openSync()")?;
Lints and clippy
tools/lint.jsrunscargo clippy --all-targets.- Many crates include a
clippy.tomlthat adds extra restrictions (e.g.,cli/clippy.tomlis 2,625 bytes of project-specific rules). Honor those — they're there for a reason. - The custom JS-side lint plugins live in
tools/lint_plugins/.
JavaScript / TypeScript conventions
Numbered files
In ext/web/, ext/fetch/, runtime/js/, etc., files are prefixed with two-digit numbers (00_*, 01_*, ...). The numbers control snapshot load order; lower numbers load first. New files should slot into the existing numbering — pick a number after all dependencies and before all dependents.
primordials / safe globals
Runtime JS code does not use the standard prototype-bound globals (Array.prototype.map, JSON.parse, etc.) because user code can monkey-patch them. Instead, code uses primordials — captured references to the original built-ins, available via deno_core's primordials support.
const { ArrayPrototypeMap, JSONParse } = primordials;
const result = ArrayPrototypeMap(arr, fn);This pattern is a hard rule for code that runs before user code (most extension JS). When in doubt, look at the file you're editing — if it imports from primordials, follow suit.
TypeScript types for ops
Op signatures are declared in tools/ops.d.ts and per-extension internal.d.ts files. When you add a new op, also add its TypeScript declaration so the calling JS code is type-checked.
Public-facing JS APIs
User-facing JS APIs (Deno.*, the web platform globals, node:* modules) are documented inline with TSDoc comments. The doc generator pulls from these to produce https://docs.deno.com. Keep them up to date when changing behavior.
Testing conventions
- New user-visible behavior → spec test under
tests/specs/. - New
Deno.*JS API → unit test undertests/unit/. - New
node:*polyfill → unit test undertests/unit_node/and possibly enable a Node compat test. - New op or lower-level Rust function → inline
#[cfg(test)]test in the same file. - New LSP capability → add to
tests/integration/lsp_tests.rs(yes, the 20K-line one).
Cross-cutting patterns
CliFactory
Anything in cli/ that needs configured services (module loader, npm resolver, type checker, …) goes through cli/factory.rs::CliFactory. The factory lazily constructs services from the parsed Flags and handles config-file discovery. Don't construct these services directly in subcommand code — ask the factory.
Permission checks
Privileged ops always go through runtime/permissions/lib.rs::PermissionsContainer. The pattern:
let permissions = state.borrow_mut::<PermissionsContainer>();
permissions.check_read(&path, "api_name")?;The second argument is the API name shown in the prompt and in error messages. Make it match the user-facing function.
Flags are the source of truth
CLI flags defined in cli/args/flags.rs produce a typed Flags struct that is threaded into the rest of the CLI. New behavior that needs configuration should add a flag (and a matching deno.json field if it should be persistable) rather than reading env vars directly. Env-var-only escape hatches are reserved for things like DENO_LOG and DENO_DIR.
Sys traits
cli/lib.rs defines pub type CliSys = sys_traits::impls::RealSys; — the abstraction over filesystem/process/env that lets CLI code be tested without touching the real OS. Lower-level crates (libs/resolver, libs/cache_dir, etc.) take a generic Sys: ... parameter so tests can plug in fakes.
Style nits
- Keep functions small and well-named; the codebase favors many short functions over long ones.
- Comments explain why, not what. The code itself shows what.
cargo fmtanddprintare not negotiable —./x verifychecks them.- Conventional commit prefixes in PR titles:
feat(scope):,fix(scope):,perf(scope):, etc. - No unnecessary
unsafe. Whenunsafeis needed, comment why and what invariants the caller must uphold.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.