Interpreters, compilers, async runtimes, parsers and low-level systems code.
406 instructions from 17 repositories. Last updated 2025-10-23.
Variable, method, and type names should accurately reflect their actual purpose, content, and behavior. Avoid generic or misleading names that don’t match the underlying functionality.
Key principles:
visible_input_methods not visible_input_method for a Vecclosed_any_websocket not canceled_any_fetch when closing websocketshit_test_result not cached_hit_test_result when it’s not always cachedarea not size when measuring area specificallyExample:
// Bad - misleading name
let cached_hit_test_result = match event.event { ... }
// Good - accurate name
let hit_test_result = match event.event { ... }
// Bad - singular for collection
visible_input_method: Vec<EmbedderControlId>,
// Good - plural for collection
visible_input_methods: Vec<EmbedderControlId>,
Names should immediately communicate what the code actually does, not what it might do or what it was originally intended to do.
Identify and eliminate computational work that serves no purpose, particularly for inactive, hidden, or irrelevant elements. This includes early returns for non-visible components, lazy initialization of expensive resources, and preventing work during inappropriate timing.
Key strategies:
Example from display list processing:
// Before: Always process display lists
fn handle_display_list(&mut self, webview_id: WebViewId, display_list: DisplayList) {
self.process_display_list(webview_id, display_list);
}
// After: Skip processing for hidden webviews
fn handle_display_list(&mut self, webview_id: WebViewId, display_list: DisplayList) {
if !self.webview_renderers.is_shown(webview_id) {
return debug!("Ignoring display list for hidden webview {:?}", webview_id);
}
self.process_display_list(webview_id, display_list);
}
Example from variable initialization:
// Before: Create display immediately
let display = path.display();
if some_condition {
println!("Error with {}", display);
}
// After: Create display only when needed
if some_condition {
let display = path.display();
println!("Error with {}", display);
}
This optimization prevents wasted CPU cycles, reduces memory pressure, and improves overall application responsiveness by ensuring computational resources are only used when they contribute to meaningful work.
Replace .unwrap() calls and sentinel values with safe null handling patterns to prevent runtime panics and improve code robustness.
Key Issues:
.unwrap() on operations that could fail, especially downcasts and IPC operationsInvalid or None = -1 instead of proper Option typesSafe Patterns:
if let Some(value) = optional_value instead of .unwrap().expect("meaningful message") when unwrapping is truly safeOption<T> types? operator for early returns with OptionsOption<T> instead of default values when absence is meaningfulExample:
// ❌ Unsafe - could panic
let video_elem = self.downcast::<HTMLVideoElement>().unwrap();
video_elem.resize(width, height);
// ✅ Safe pattern
if let Some(video_elem) = self.downcast::<HTMLVideoElement>() {
video_elem.resize(width, height);
}
// ❌ Sentinel value antipattern
pub enum LargestContentfulPaintType {
None = -1,
// ...
}
// ✅ Proper Option usage
pub fn get_lcp_candidate() -> Option<LCPCandidate> {
// ...
}
This prevents runtime crashes and makes null states explicit in the type system, improving both safety and code clarity.
Prioritize algorithmic efficiency by choosing operations and data structures that minimize computational complexity. Look for opportunities to replace O(n) operations with O(1) alternatives, select appropriate data structures for the use case, and avoid unnecessary algorithmic overhead.
Key optimization strategies:
is_some_and() and if let instead of complex nested operationsExample of complexity optimization:
// Instead of O(n) traversal:
fn get_root_slow(&self) -> Self {
let mut current = self.clone();
while let Some(parent) = current.parent() {
current = parent;
}
current
}
// Provide O(1) direct access:
fn get_root_node(&self) -> Self {
// Often possible to get root in O(1) if in shadow/document tree
self.document().root_element()
}
This approach reduces computational overhead and improves performance, especially in frequently called code paths involving DOM traversal, data structure operations, and rendering algorithms.
Replace .unwrap() and panic!() calls with proper error handling to prevent crashes and improve robustness. When encountering potential failures, choose one of three approaches:
Result types.expect() with clear, actionable messages if crashing is truly intendedExamples:
Instead of:
let result = receiver.recv().unwrap().unwrap();
Use propagation:
fn get_result(&self) -> Result<SomeType, Error> {
let result = receiver.recv().map_err(|e| Error::IpcFailure)?
.map_err(|e| Error::OperationFailed)?;
Ok(result)
}
Instead of:
devtools_chan.send(msg).unwrap();
Use logging:
if let Err(e) = devtools_chan.send(msg) {
log::error!("DevTools send failed: {e}");
}
Instead of:
let components = string.split('x').collect::<Vec<_>>();
Ok(Some(Size2D::new(components[0], components[1])))
Use graceful handling:
let (width, height) = string.split_once('x')
.ok_or(ParseResolutionError::InvalidFormat)?;
This approach prevents unexpected crashes, especially in content processes, and provides better debugging information when failures occur.
Prioritize code readability by reducing complexity through better organization and clearer patterns. Use early returns to avoid deep nesting, extract methods to break down large functions, and choose appropriate data structures over generic ones.
Key practices:
match expressions and iterator methods over manual loopsExample of improving nested code:
// Instead of deep nesting:
if ancestor.is::<Document>() {
true
} else if ancestor.is::<Element>() {
let ancestor = ancestor.downcast::<Element>().unwrap();
// ... complex logic
} else {
false
}
// Use early returns:
if ancestor.is::<Document>() {
return true;
}
let Some(ancestor) = ancestor.downcast::<Element>() else {
return false;
};
// ... simplified logic
This approach makes code easier to follow, test, and maintain by reducing cognitive load and making the control flow more explicit.
Ensure algorithmic implementations properly handle edge cases and boundary conditions before considering them complete. Many algorithm failures stem from insufficient consideration of special inputs, parameter combinations, or state transitions that occur at the boundaries of normal operation.
Key areas to validate:
[0xFF] byte in encoding scenarios)last=false in streaming operations)Example from encoding algorithms:
// Problem: encoding_rs::Decoder fails with single invalid byte when last=false
// The algorithm includes malformed bytes in bytes_read count, causing issues
// when transitioning to flushing state (last=true) with empty remaining slice
let result = decoder.decode_to_string_without_replacement(input, last=false);
// Edge case: single invalid byte [0xFF] doesn't return DecoderResult::Malformed
Before marking algorithmic work as complete, systematically test edge cases that could cause unexpected behavior in production. Document known limitations and file follow-up issues for incomplete algorithm implementations.
All public items, struct fields, and methods must have comprehensive documentation. When implementing web specifications, include the actual specification text in comments rather than just links, as specifications can change over time.
Required Documentation:
pub items must have rustdoc commentsSpecification References: Include actual specification text in comments, not just links:
// ❌ Avoid link-only references
// Step 3 - 5.
// <https://w3c.github.io/webdriver/#dfn-deserialize-a-web-window>
// ✅ Include specification text
// Step 3: Let browsing context be the browsing context whose window handle is
// reference, or null if no such browsing context exists.
// Step 4: If browsing context is null or not a top-level browsing context,
// return error with error code no such window.
// Step 5: Return success with data browsing context's associated window.
Comprehensive Field Documentation:
// ✅ Document all fields with purpose and context
pub struct TextDecoderStream {
reflector_: Reflector,
/// Document-specific data required to fetch a web font.
#[ignore_malloc_size_of = "Rc is hard"]
decoder: Rc<TextDecoderCommon>,
/// The associated transform, as per
/// <https://streams.spec.whatwg.org/#generictransformstream>
transform: Dom<TransformStream>,
}
This ensures code is self-documenting, maintainable, and provides clear context for future developers and specification compliance.
Use appropriate conditional statements in CI workflows to ensure steps execute under the right circumstances and handle different environments gracefully.
Key practices:
if: ${{ always() }} for cleanup/finalization steps that must run regardless of previous step failuresif: ${{ runner.environment != 'self-hosted' }} for steps that should only run on certain runner typesExample from the discussions:
- name: Publish artifact marking this job as done
uses: actions/upload-artifact@v4
if: ${{ always() }} # Ensures cleanup runs even if job fails
with:
# artifact configuration
- name: Setup Python
if: ${{ runner.environment != 'self-hosted' }} # Only on GitHub-hosted
uses: ./.github/actions/setup-python
- name: Change Mirror Priorities
if: ${{ runner.environment != 'self-hosted' }} # Skip on self-hosted
uses: ./.github/actions/apt-mirrors
This approach prevents workflow failures due to missing infrastructure, ensures proper cleanup, and optimizes resource usage by skipping unnecessary steps on different runner types.
When working with configuration parameters and values, follow these best practices to ensure maintainable and correct configuration handling:
Use collections for multiple related values: When checking against multiple related configuration values, use the in operator with a list or set rather than chaining multiple OR conditions. This makes the code more readable and easier to maintain when adding new values.
Use tool-specific parameter names: Different tools may use different parameter names for similar concepts. Always verify and use the correct parameter names for the specific tool being invoked to avoid configuration errors.
Example from the codebase:
# Good: Use 'in' operator for multiple related values
return self.is_custom() and (self.profile in ["production", "production-stripped"])
# Good: Use tool-specific parameter names
# For nextest, use --cargo-profile instead of --profile
args += ["--cargo-profile", build_type.profile]
This approach reduces maintenance overhead when adding new configuration values and prevents configuration mismatches when working with different tools that have similar but distinct parameter naming conventions.
Ensure all security-related functionality includes complete validation checks and proper testing. This applies to both cryptographic operations and security policy configurations.
For cryptographic operations, implement all required validation steps as specified in standards. For example, in EdDSA signature verification:
// Step 2: Validate key data
if (key.representsInvalidPoint() || key.isSmallOrderElement()) {
return false;
}
// Step 3: Validate signature point R
if (signatureR.representsInvalidPoint() || signatureR.isSmallOrderElement()) {
return false;
}
For security policies like Content Security Policy, ensure all required directives are properly configured and tested:
<!-- Ensure all necessary permissions are included -->
Content-Security-Policy: sandbox allow-forms allow-scripts
Always verify that security validation logic is complete by checking against specifications and testing edge cases. Missing validation steps can create security vulnerabilities, whether in cryptographic implementations or policy configurations.
When creating network requests, ensure all security-sensitive parameters are properly configured including origin, referrer, CORS mode, and destination. Incomplete request configuration can lead to test failures, security vulnerabilities, and incorrect cross-origin behavior.
Always specify:
Example of proper request configuration:
let request = RequestBuilder::new(
state.webview_id,
url.clone().into(),
Referrer::ReferrerUrl(document_context.document_url.clone()),
)
.destination(Destination::Font)
.mode(RequestMode::CorsMode)
.origin(ImmutableOrigin::new(url.origin()));
Be particularly careful with CORS mode settings as they can cause failures when loading cross-origin resources. Test thoroughly when modifying request builder parameters, as incomplete configuration often manifests as test failures that may not be immediately obvious.
When integrating with external systems that receive repository or organizational information, always implement server-side validation to ensure the provided data belongs to authorized organizations or repositories. This prevents potential abuse where attackers could exploit organizational resources by spoofing repository information.
The receiving system should validate that repository names, organization identifiers, or similar contextual data are constrained to expected values before processing requests or allocating resources.
Example from CI runner integration:
# Action passes repository info to external monitor
echo "qualified_repo=${{ github.repository }}" | tee -a "$artifact_path"
The external monitor should validate that github.repository belongs to the authorized organization (e.g., “servo/*”) before proceeding with runner allocation or resource provisioning. Without this validation, malicious actors could potentially abuse organizational infrastructure by providing crafted repository information.
Design testing tool interfaces to be both flexible and discoverable. Support multiple keyword variations for test-related functionality and provide pass-through parameter options to enable advanced debugging scenarios.
When implementing testing commands or parsers, consider:
--stress-count for reproducing flaky tests)Example implementation:
# Support multiple keywords for discoverability
elif any(word in s for word in ["cov", "coverage", "test-coverage"]):
return JobConfig("Coverage", Workflow.COVERAGE)
# Allow pass-through parameters for flexibility
@CommandArgument("params", nargs="...", help="Command-line arguments to be passed through to test runner")
This approach improves developer experience by making testing tools more accessible and providing the flexibility needed for complex debugging scenarios.
Prevent borrow conflicts by collecting or cloning data before operations that may trigger garbage collection, call into JavaScript, or create additional borrows. This is especially critical when holding mutable borrows (borrow_mut()) that could conflict with operations that need to access the same data.
The pattern involves identifying operations that might cause borrow conflicts and restructuring code to release borrows before those operations execute.
Example of the problem:
// BAD: borrow_mut is still active when calling jsval_to_webdriver (which can trigger GC)
fn WebdriverCallback(&self, cx: JSContext, val: HandleValue, realm: InRealm, can_gc: CanGc) {
let rv = jsval_to_webdriver(cx, &self.globalscope, val, realm, can_gc);
let opt_chan = self.webdriver_script_chan.borrow_mut().take();
if let Some(chan) = opt_chan {
let _ = chan.send(rv);
}
}
Corrected approach:
// GOOD: Take the channel first, then call the potentially conflicting operation
fn WebdriverCallback(&self, cx: JSContext, value: HandleValue, realm: InRealm, can_gc: CanGc) {
if let Some(chan) = self.webdriver_script_chan.borrow_mut().take() {
let result = jsval_to_webdriver(cx, &self.globalscope, value, realm, can_gc);
let _ = chan.send(result);
}
}
Another pattern - collecting before iteration:
// GOOD: Collect vector before starting loop to avoid borrow hazards
pub(crate) fn fire_queued_messages(&self, can_gc: CanGc) {
let messages: Vec<_> = self.queued_worker_tasks.borrow_mut().drain(..).collect();
for msg in messages {
// Process message with can_gc argument safely
}
}
This pattern is essential in garbage-collected environments where operations can trigger collection cycles that conflict with active borrows.
Design APIs with appropriate parameter types, place conversion functions as trait implementations in the correct modules, and reuse existing patterns instead of creating new abstractions.
Key principles:
Use appropriate parameter types: Prefer &[T] over &Vec<T> for function parameters to accept more input types and improve API flexibility.
Place conversions as trait implementations: When creating conversion functions, implement them as traits (like From<&str>) in the module where the target type is defined, rather than standalone functions elsewhere.
Reuse existing patterns: Before creating new IPC messages, traits, or abstractions, check if existing patterns can be extended or reused. Avoid adding dependencies just for cleaner conversions when simple functions suffice.
Prefer functional patterns: Use map(), unwrap_or_else(), and similar combinators instead of imperative if-let chains when it improves readability.
Example of good API design:
// Good: slice parameter, functional style
fn process_link_headers(link_headers: &[LinkHeader]) -> Vec<ProcessedLink> {
link_headers.iter()
.map(|header| process_header(header))
.collect()
}
// Good: trait implementation in type's module
impl From<&str> for CorsSettings {
fn from(token: &str) -> Self {
match_ignore_ascii_case! { token,
"anonymous" => CorsSettings::Anonymous,
"use-credentials" => CorsSettings::UseCredentials,
_ => CorsSettings::Anonymous,
}
}
}
This approach creates more flexible, discoverable, and maintainable APIs while avoiding unnecessary complexity.
Methods that access raw memory buffers, perform pointer operations, or interact with foreign function interfaces must be explicitly marked as unsafe to prevent memory safety vulnerabilities. Conversely, remove unnecessary unsafe blocks when calling safe functions to maintain code clarity and prevent masking actual unsafe operations.
Key principles:
unsafe when they access raw memory that could be invalidated (e.g., borrowed string buffers that could be freed)transmute operations that create technical debt and potential memory safety issues during library upgradesunsafe blocks when the called functions are actually safeExample of proper unsafe marking:
// BAD: Missing unsafe marker for dangerous operation
pub(super) fn bytes<'a>(&'a self) -> EncodedBytes<'a> {
// Accesses raw JS string buffer that could be invalidated
}
// GOOD: Properly marked as unsafe
pub(super) unsafe fn bytes<'a>(&'a self) -> EncodedBytes<'a> {
// Caller must ensure JS string won't be mutated/freed
}
// BAD: Unnecessary unsafe block
unsafe { root_from_handlevalue::<TrustedScriptURL>(value, cx).is_ok() }
// GOOD: Remove unsafe when function is safe
root_from_handlevalue::<TrustedScriptURL>(value, cx).is_ok()
This prevents memory corruption vulnerabilities and maintains Rust’s memory safety guarantees while clearly communicating risk to API consumers.
When executing external commands via subprocess, don’t rely solely on return codes for error detection. Some tools may return 0 even when they fail. Instead, check multiple error indicators including return codes, stderr output, and stdout content to ensure robust error handling.
Use higher-level subprocess methods like subprocess.run() or subprocess.check_output() when possible, as they provide better built-in error handling. When using subprocess.Popen(), always examine both the return code and error output:
# Instead of only checking return code:
if self.hdc_process.returncode != 0:
logging.warning(f"HDC reverse port forwarding failed: {stderr.decode()}")
# Check both return code AND error message content:
stdout, stderr = self.hdc_process.communicate()
if self.hdc_process.returncode != 0 or "error" in stderr.decode().lower():
logging.warning(f"HDC reverse port forwarding failed: {stderr.decode()}")
return False
This approach is especially important when working with external tools that may not follow standard Unix conventions for exit codes.
When working with Option types, prefer efficient and safe access patterns over unnecessary operations. Use .as_ref().unwrap() instead of .clone().unwrap() to avoid unnecessary cloning when you only need a reference to the contained value. When possible, prefer safe alternatives like to_string_lossy() over unwrap(), or use early returns with the ? operator to handle None cases gracefully.
Example of preferred patterns:
// Prefer this - no unnecessary clone
format!("version = \"{}\"", self.version.as_ref().unwrap())
// Instead of this - unnecessary clone
format!("version = \"{}\"", self.version.clone().unwrap())
// Prefer safe alternatives
test_file_name.to_string_lossy()
// Instead of unwrap when failure is possible
test_file_name.into_string().unwrap()
// Use early returns for None handling
Some(crate_path.parent())?;
// Instead of unwrap that can panic
crate_path.parent().unwrap()
This approach reduces unnecessary allocations, prevents potential panics, and makes code more robust when dealing with optional values.
Choose efficient algorithms and appropriate data structures based on access patterns and computational requirements. Use built-in comparison methods like cmp() to avoid redundant checks, select data structures that match usage patterns (HashSet for lookups, Vec for iteration), and ensure algorithmic correctness by handling edge cases properly.
Key practices:
match with cmp() for version/ordering comparisons instead of multiple if-else conditionssize_hint() returns remaining elements countExample of efficient comparison:
// Instead of multiple if-else conditions
match new_version.cmp(¤t_version) {
Ordering::Less => eprintln!("Warning: new version is lower than current!"),
Ordering::Greater => println!("Bumping version {current_version} to {new_version}"),
Ordering::Equal => println!("Keeping version {current_version}"),
}
Break down complex code structures into simpler, more readable forms. This includes avoiding deep nesting, extracting complex logic into helper functions, simplifying boolean expressions, and reducing code duplication.
Key practices:
match statements instead of multiple if conditions for the same variable!(a || b) instead of separate conditions)unwrap_or() instead of imperative if/else blocksExample of improvement:
// Before: Complex nested conditions
if filename == "package.json" {
// logic
}
if filename == "package.json" || filename == "pom.xml" {
// more logic
}
// After: Clear match statement
match (generate_opts.author_url, filename) {
(Some(url), "package.json" | "pom.xml") => replacement.replace(AUTHOR_URL_PLACEHOLDER, url),
(None, "package.json") => replacement.replace(AUTHOR_URL_PLACEHOLDER_JS, ""),
(None, "pom.xml") => replacement.replace(AUTHOR_URL_PLACEHOLDER_JAVA, ""),
_ => {},
}
This approach improves code maintainability, reduces cognitive load, and makes the codebase more approachable for new contributors.
Always specify exact versions for dependencies and avoid pointing to moving targets like “master” branches or “latest” tags in configuration files. This ensures reproducible builds and prevents unexpected breakages when upstream dependencies change.
When configuring dependencies across multiple components of a project, use consistent version numbers. For example, if your main project uses Kotlin 2.1.20, ensure all submodules and build configurations use the same version:
// In build.gradle - use consistent versions
plugins {
id("com.android.library") version "8.7.1" apply false
id("com.android.application") version "8.7.1" apply false
id 'org.jetbrains.kotlin.android' version '2.1.20' apply false // Match react-native version
}
# In podspec files - pin to specific tags, not branches
spec.source = {
:git => 'https://github.com/facebook/yoga.git',
:tag => spec.version.to_s, // Use tagged version
# :branch => "master", // Avoid this - causes unpredictable fetches
}
This practice prevents version drift, ensures all team members build with identical dependencies, and makes it easier to track which specific versions were used in each release.
Documentation should be clear, accurate, and provide meaningful information to users. Avoid misleading descriptions, template text that doesn’t add value, and unclear explanations that leave users confused about functionality.
Key practices:
Example of unclear vs clear documentation:
// Unclear - misleading about actual behavior
/// Don't force rebuild the parser when `--grammar_path` is specified
// Clear - accurately describes the relationship
/// `--grammar_path` implies force rebuild
Documentation should help users understand not just what something does, but how and when to use it effectively.
When modifying API type definitions, ensure all related definition files are updated consistently and dependencies are properly managed. API types often exist in multiple formats (TypeScript definitions, JSON schemas, etc.) and must be kept in sync to maintain a coherent client interface.
Key practices:
Partial<T> for optional parameters rather than requiring all properties)Example from the discussions:
// When adding a new rule type
type ReservedRule = { type: 'RESERVED'; content: Rule; context_name: string };
// Also update grammar.schema.json with corresponding schema definition
// Use proper type modifiers for optional parameters
export default function MainModuleFactory(options?: Partial<EmscriptenModule>): Promise<MainModule>;
// Rather than requiring all EmscriptenModule properties
This ensures that API consumers have accurate, complete type information and that the API surface remains consistent across all definition formats.
Use explicit types instead of auto unless the type is obvious from the right-hand side of the assignment or impossible to spell. The LLVM coding standards recommend avoiding auto in most cases to improve code readability and maintainability.
When to use auto:
auto *CI = dyn_cast<CallInst>(I)for (auto it = container.begin(); ...)auto result = make_unique<MyClass>()When to avoid auto:
StringRef name = getValue() instead of auto name = getValue()unsigned count = getCount() instead of auto count = getCount()MLIRContext *context = builder.getContext() instead of auto context = builder.getContext()Example:
// Bad - type not obvious
auto name = getValue();
auto count = getElementCount();
auto context = builder.getContext();
// Good - explicit types improve readability
StringRef name = getValue();
unsigned count = getElementCount();
MLIRContext *context = builder.getContext();
// Good - auto appropriate here
auto *CI = dyn_cast<CallInst>(instruction);
for (auto it = container.begin(); it != container.end(); ++it) { ... }
This practice ensures code remains readable without IDE assistance and makes type information immediately available to reviewers and maintainers.
Choose names that clearly describe the purpose, behavior, and content of functions, variables, and constants. Names should be self-documenting and unambiguous to other developers.
Function Names: Use verbs that accurately describe what the function does. For example, prefer getOrGenImplicitDefaultDeclareMapper over genImplicitDefaultDeclareMapper when the function both retrieves existing items and generates new ones. Similarly, use canPassByValue instead of isLiteralType when checking if a variable can be passed by value rather than just checking for literal types.
Variable Names: Choose names that clearly indicate the variable’s purpose and avoid ambiguity. For example, use hasMapHdr and hasStringHdr instead of hasMap and hasString when referring to header files, since the latter are common names that usually don’t refer to headers.
Constants: Define named constants instead of using magic strings or literals directly in code. Replace inline strings like "aligned_alloc" or "localabstract" with properly declared constants:
// Instead of magic strings
if (callOp.getCallee() != "aligned_alloc")
// Use named constants
static constexpr char kAlignedAllocFunc[] = "aligned_alloc";
if (callOp.getCallee() != kAlignedAllocFunc)
Misleading Names: Avoid names that suggest different functionality than what is actually implemented. For instance, don’t use asan_dispatch_apply_f_block for a function that handles regular function callbacks rather than blocks - prefer asan_dispatch_apply_f_callback instead.
This approach makes code more maintainable, reduces the likelihood of errors, and improves code readability for all team members.
Minimize performance overhead by avoiding unnecessary memory allocations, expensive data structures, and redundant object creation. Choose appropriate data types and parameter passing methods based on actual requirements rather than convenience.
Key practices:
int32_t[] instead of SmallVector<APInt> for fixed-size arrays)StringRef instead of const std::string&, std::move() for transferring ownership)TrackingVH with visited sets where possible)std::stringstream or formatv() instead of repeated concatenation)Example of inefficient vs efficient approaches:
// Inefficient: Creates many temporary strings and copies
std::string result = "{ ";
for (size_t i = 0; i < items.size(); ++i) {
result += "\"" + items[i].first + "\", " + items[i].second;
if (i < items.size() - 1) result += ", ";
}
// Efficient: Uses stringstream to avoid copies
std::stringstream ss;
ss << "{ ";
for (size_t i = 0; i < items.size(); ++i) {
ss << "\"" << items[i].first << "\", " << items[i].second;
if (i < items.size() - 1) ss << ", ";
}
std::string result = ss.str();
Profile performance-critical code paths to identify where expensive operations like TrackingVH creation or unnecessary allocations are causing bottlenecks, then replace them with more efficient alternatives.
When implementing algorithmic transformations and optimizations, use cost analysis and profitability heuristics to guide decision-making rather than applying transformations blindly. This includes choosing appropriate data structures based on access patterns, placing optimizations at the correct architectural level, and validating that algorithmic changes actually improve performance.
Key principles:
Data structure selection: Choose between alternatives based on access patterns. For example, use a map when you need efficient lookups and only want the most recent entry, but use a vector when linear iteration is acceptable and the number of elements is small.
Optimization placement: Place algorithmic optimizations where they can be most effective. Constant folding should be in generic optimization passes rather than specialized locations, and profitability checks should override general heuristics when needed.
Performance validation: Always measure the impact of algorithmic changes. Cost models can be wrong, leading to performance regressions that need to be detected and addressed.
Example from loop transformations:
// Use profitability analysis to guide loop interchange
bool shouldInterchange = profitabilityCheck(outerLoop, innerLoop);
if (shouldInterchange && canVectorizeInnerLoop(outerLoop)) {
// Move vectorizable loop to innermost position
interchangeLoops(outerLoop, innerLoop);
}
The goal is to make algorithmic decisions based on measurable criteria rather than assumptions, ensuring that optimizations actually improve performance for the target use cases.
Organize code to minimize header dependencies and improve compilation times. Use forward declarations instead of includes when possible, avoid unnecessary dependencies, and follow proper header organization patterns.
Key practices:
static declarations in header files as they create separate instances in each translation unitExample:
// Good: Use forward declaration in header
// MyClass.h
class WarnUnusedResultAttr; // Forward declaration
class MyClass {
WarnUnusedResultAttr* attr;
};
// MyClass.cpp - actual include in implementation
#include "clang/AST/Attr.h"
// Bad: Heavy include in header
// MyClass.h
#include "clang/AST/Attr.h" // Unnecessary dependency
This approach reduces compilation times, minimizes rebuild cascades when headers change, and creates cleaner module boundaries.
Choose specific, meaningful names that clearly convey purpose and follow established codebase patterns. Avoid generic terms that could apply to many different concepts.
Key principles:
Avoid overloaded generic terms: Instead of generic names like kind() that “mean sooooo many different things,” use specific descriptive names like templateKind() or nameKind() that clearly indicate what type of classification is being provided.
Follow capitalization conventions: Variable names should start with capital letters where required by the codebase style. For example, vAddr should be VirtualAddr for better readability and consistency.
Use positive boolean naming: Prefer positive constructions like EnableMISchedLoadClustering over negative ones like DisableMISchedLoadClustering. This creates clearer auto-generated method names like enableXXX() rather than confusing double negatives.
Follow established patterns: Synthetic variants should use lowercase suffixes, attribute spellings should match existing conventions, and naming should be consistent with similar constructs in the codebase.
Prefer explicit over implicit: Use fully qualified names rather than using declarations when it improves clarity, especially for types that might be ambiguous in context.
Example:
// Avoid generic, overloaded terms
TemplateNameKind kind() const; // ❌ Generic "kind"
// Use specific, descriptive names
TemplateNameKind templateKind() const; // ✅ Clear purpose
// Follow positive naming for booleans
def TuneDisableMISchedLoadClustering; // ❌ Negative construction
def TuneEnableMISchedLoadClustering; // ✅ Positive, clear methods
Ensure configuration options are complete and properly validated before use. When implementing configuration parsing or option handling, verify that all expected cases are covered and add appropriate validation checks for user inputs.
Key practices:
Complete option coverage: When implementing configuration switches or parsers, ensure all documented or expected options are handled. Missing cases can lead to silent failures or unexpected behavior.
Input validation: Add sanity checks for user-provided configuration values, especially when they override compiler-determined settings.
Required attribute verification: When configuration depends on specific attributes or flags being present, validate their existence early in the process.
Example from MinGW CRT DLL configuration:
// Ensure all expected CRT DLL versions are handled
Opts.MinGWCRTDll = llvm::StringSwitch<enum LangOptions::WindowsCRTDLLVersion>(A->getValue())
.StartsWithLower("crtdll", LangOptions::WindowsCRTDLLVersion::CRTDLL)
.StartsWithLower("msvcrt10", LangOptions::WindowsCRTDLLVersion::MSVCRT10)
// ... other cases ...
.StartsWithLower("msvcrt-os", LangOptions::WindowsCRTDLLVersion::MSVCRT_OS) // Don't miss expected cases
.Default(LangOptions::WindowsCRTDLLVersion::Unknown); // Handle unexpected input
Example of validation for user input:
// Validate user-specified vector sizes
if (!inputVectorSizes.empty()) {
assert(inputVectorSizes.size() == expectedSize &&
"Incorrect number of input vector sizes");
// Add runtime validation for production code
}
This prevents configuration-related bugs and ensures robust handling of both expected and edge-case inputs.
When implementing algorithms, be mindful of computational complexity and optimize for efficiency by avoiding expensive operations and reusing existing computations where possible.
Key principles:
Avoid expensive object operations: When sorting or manipulating heavy objects, work with pointers or lightweight references instead of the objects themselves. For example, when sorting a collection of large structures, sort an array of pointers rather than moving the actual objects.
Reuse existing computations: Before performing expensive calculations, check if equivalent results already exist that can be reused with minimal adjustment. Look for opportunities to reuse intermediate results rather than recomputing from scratch.
Ensure deterministic ordering: Use consistent ordering (e.g., sorting) for data structures to avoid non-deterministic behavior that can lead to inconsistent optimization results or algorithmic outcomes.
Consider phase ordering: Be aware that the sequence of applying algorithmic phases can significantly impact the final result quality. Design algorithms with careful consideration of when each optimization or transformation should be applied.
Example from the codebase:
// Instead of sorting heavy objects directly:
// std::sort(heavyObjects.begin(), heavyObjects.end());
// Sort pointers to avoid expensive object movement:
SmallVector<LSRUse *, 16> sortedPointers;
for (auto &use : heavyObjects)
sortedPointers.push_back(&use);
std::sort(sortedPointers.begin(), sortedPointers.end(), comparator);
// Reuse existing computations when possible:
if (existingResult && canReuseWithOffset(existingResult, offset)) {
return adjustWithOffset(existingResult, offset);
}
This approach reduces computational overhead while maintaining algorithmic correctness and improving overall performance.
Tests should be minimal and focused on the specific functionality being verified. Remove unnecessary passes, tools, or operations that aren’t directly related to what’s being tested to reduce noise and improve clarity.
Key principles:
llvm-as -disable-output for verification tests instead of more complex alternatives)Example of improvement:
// Before: Includes unnecessary canonicalizer pass
// RUN: mlir-opt %s -convert-complex-to-rocdl -canonicalize | FileCheck %s
// After: Focuses only on the conversion being tested
// RUN: mlir-opt %s -convert-complex-to-rocdl | FileCheck %s
This approach makes tests easier to understand, maintain, and debug while ensuring they remain effective at catching regressions.
Select the most efficient string type based on ownership and lifetime requirements to optimize memory usage and prevent safety issues. Use StringRef for parameters and when you don’t need to own the string data, std::string when you need ownership and persistence, and avoid storing Twine objects as member variables.
Key guidelines:
StringRef for function parameters when you only need to read the stringstd::string for member variables when you need to own and persist the dataTwine as a member variable - it doesn’t own its underlying data and can cause memory violationsExample from the discussions:
// Bad: Unnecessary string copy for constant data
class InvalidRSMetadataFormat {
std::string ElementName; // If always called with constants
};
// Good: Use StringRef when appropriate
class InvalidRSMetadataFormat {
StringRef ElementName; // For constant strings
};
// Bad: Dangerous storage of Twine
class GenericRSMetadataError {
Twine Message; // Can cause memory violations
};
// Good: Own the string data when needed
class GenericRSMetadataError {
std::string Message; // Safe ownership
};
This optimization reduces memory allocations, prevents unnecessary copying, and avoids potential memory safety issues while maintaining code correctness.
When designing enums or working with type casting, explicitly define and handle invalid states rather than relying on undefined behavior. Use designated invalid values that stand out clearly, and employ equality assertions for type compatibility checks.
For enums, avoid gaps in value sequences and use distinctive invalid values:
// Instead of skipping values:
enum WasmAddressType { Memory = 0x00, Object = 0x01, Invalid = 0x03 };
// Use clear, distinctive invalid values:
enum WasmAddressType { Memory = 0x00, Object = 0x01, Invalid = 0xff };
For type casting scenarios, use equality assertions when types must be compatible:
// Instead of just checking size compatibility:
static_assert(sizeof(Barrier) <= sizeof(pthread_barrier_t));
// Assert exact equality when casting between types:
static_assert(sizeof(Barrier) == sizeof(pthread_barrier_t));
This approach prevents undefined behavior, makes invalid states explicit and detectable, and ensures type safety through clear assertions rather than implicit assumptions.
Design APIs with complexity that matches their actual use cases and adopt established patterns that improve clarity. Avoid over-engineering interfaces when simpler alternatives suffice, and prefer modern, well-established patterns over legacy approaches.
When designing function parameters, choose the simplest form that meets current requirements rather than anticipating future complexity that may never materialize. For example, use boolean flags instead of function callbacks when the callback would only return true/false.
For error handling, prefer modern patterns like Expected
Example of simplification:
// Instead of complex callback for simple cases:
bool LoopRotation(Loop *L, ...,
function_ref<bool(Loop *, ScalarEvolution *)> ProfitabilityCheck);
// Use simple boolean when that's all that's needed:
bool LoopRotation(Loop *L, ..., bool IgnoreProfitability);
Example of modern error handling:
// Instead of output parameter + status:
static Status ResolveDeviceID(const std::string &device_id,
std::string &resolved_device_id);
// Use Expected<T> pattern:
static llvm::Expected<std::string> ResolveDeviceID(llvm::StringRef device_id);
When tests exhibit known false positives or false negatives, preserve these test cases with clear documentation rather than removing them. This practice prevents accidental “fixes” that mask underlying problems and helps track when issues are genuinely resolved.
As noted in code review discussions, removing tests for known issues can lead to situations where “someone could make a refactor/speedup patch of the check and accidentally fix FP. Without this test, developer wouldn’t know he fixed a FP with an NFC patch.”
When modifying test expectations:
Example approach:
// FIXME: This triggers a false positive: the "passed to same function" heuristic
// can't map the parameter index 1 to A and B because myprint() has no
// parameters.
// warning: 2 adjacent parameters of 'passedToSameKNRFunction' of similar type ('int')
#if 0
int myprint();
// Test case preserved to track when this FP is actually fixed
#endif
This ensures test suite integrity and prevents regression in issue tracking.
Always explicitly specify byte order (endianness) in network protocol documentation and implementations. Even when a specification mandates a particular endianness, make it clear in your documentation to avoid confusion and implementation errors.
When documenting network protocols, include explicit statements about byte ordering, especially when the protocol endianness might differ from the host system’s native endianness. This prevents ambiguity and helps developers implement the protocol correctly.
Example from qWasmCallStack documentation:
Get the Wasm callback for the given thread id. This returns a hex-encoding list
of 64-bit addresses for the frame PCs. To match the Wasm specification, the
addresses are encoded in little endian byte order, even if the endian of the
Wasm runtime's host is not little endian.
This approach eliminates confusion about data representation and ensures consistent implementation across different platforms and architectures.
Log messages should include sufficient contextual information and structured formatting to enable effective debugging and filtering. This means including relevant metadata, level information, and clear prefixes that help developers understand the source and severity of logged events.
For error logging, include references to relevant objects or metadata that can provide additional context:
void log(raw_ostream &OS) const override {
OS << Message;
if (MD)
MD->printTree(OS);
}
For debug logging, include level information in prefixes to enable easy filtering:
if (DebugType) {
if (Level == 0)
Level = 1;
OsPrefix << "[" << DebugType << ":" << Level << "] ";
}
This approach allows developers to filter verbose output by level and provides rich context when debugging issues. Well-structured log messages reduce debugging time and improve the overall development experience.
Documentation should be written with clarity and completeness to serve readers who may not have deep domain expertise. Use precise, unambiguous language that avoids technical jargon when simpler alternatives exist, and provide necessary context about origins, rationale, and background information.
When writing technical documentation:
Example of improvement: Instead of: “This returns a hex-encoding list of 64-bit addresses for the frame PCs” Write: “This returns a hex-encoded list of PC values, one for each frame of the call stack”
Additionally, include contextual notes such as: “Note: This packet is an LLDB extension to the GDB remote protocol, originated from [specific runtime/system].”
Design API parameters using specific, intention-revealing types rather than generic or ambiguous ones. This improves code readability, prevents misuse, and makes the API’s behavior self-documenting.
Key principles:
Option<T> instead of default values when a parameter is truly optionalExamples of improvements:
Replace default values with Option when appropriate:
// Before: Unclear if version is required
struct Version {
#[arg(default_value = "0.0.0")]
pub version: SemverVersion,
}
// After: Clear that version is optional
struct Version {
pub version: Option<SemverVersion>,
}
Replace multiple bools with enum:
// Before: Multiple related flags
pub bump: bool,
pub bump_minor: bool,
pub bump_major: bool,
// After: Single enum parameter
pub bump: Option<BumpType>, // where BumpType = { Patch, Minor, Major }
Use specific parameters over generic ones:
// Before: Generic but unclear
fn needs_recompile(lib_path: &Path, parser_c_path: &Path, scanner_path: &Option<PathBuf>, external_files_paths: &[PathBuf])
// After: Generalized and clear
fn needs_recompile(checking_paths: &[Path])
This approach makes function signatures self-documenting and reduces the cognitive load on API consumers by making valid usage patterns explicit in the type system.
Always specify explicit configuration parameters rather than relying on default settings that may vary across different build environments or target platforms. This is particularly critical in test files where assumptions about default compiler settings, target triples, or language standards can cause failures in cross-compilation scenarios or different build configurations.
When writing tests, explicitly specify:
For example, instead of relying on default compiler behavior:
// Problematic - relies on default target triple
// RUN: %clang %s -Dheader="<stdio.h>" -E | tail -1 | FileCheck %s
// Better - explicit configuration
// RUN: %clang_cc1 -x c++ -internal-isystem %S/Inputs/include %s
Or when language features require specific standards:
// Explicit standard specification for C11 features
// RUN: %check_clang_tidy -std=c11-or-later -check-suffix=WITH-ANNEX-K %s
This approach prevents build failures when default configurations differ from expectations, such as when LLVM_DEFAULT_TARGET_TRIPLE is overridden or when different C standard versions are used.
When code expressions become complex or difficult to understand at first glance, refactor them for better readability. This can be achieved through several techniques: extracting complex expressions into well-named variables, using named regex groups instead of numeric indices, or reformatting conditional logic into more concise forms.
For complex expressions, consider extracting intermediate values:
# Instead of:
rewritten_line = (
self.current_line[: match.start(2)]
+ str(self.frame_no)
+ self.current_line[match.end(2) :]
)
# Use:
frame_no_index = match.start(2)
rewritten_line = (
self.current_line[:frame_no_index]
+ str(self.frame_no)
+ self.current_line[frame_no_index:]
)
For conditional assignments, prefer concise inline forms when they improve clarity:
# Instead of multi-line conditional blocks:
args.std = (
["c++11-or-later"]
if extension in [".cpp", ".hpp", ".mm"]
else ["c99-or-later"]
)
# Use:
args.std = [
"c++11-or-later" if extension in [".cpp", ".hpp", ".mm"] else "c99-or-later"
]
The goal is to make code self-documenting and reduce cognitive load for future readers.
Code should include documentation that explains the purpose, rationale, and context behind implementation decisions, not just what the code does. This helps future maintainers understand the reasoning and makes the codebase more maintainable.
Key areas requiring contextual documentation:
Example of good contextual documentation:
// SYCL 2020 section 5.10.1, "SYCL functions and member functions linkage":
// When a function is declared with SYCL_EXTERNAL, that macro must be
// used on the first declaration of that function in the translation unit.
// Redeclarations of the function in the same translation unit may
// optionally use SYCL_EXTERNAL, but this is not required.
if (hasFirstDeclSYCLExternal && !hasSYCLExternal) {
// Implementation logic...
}
Avoid redundant comments that merely restate what the code obviously does. Focus on the “why” rather than the “what”.
When designing APIs, prioritize clarity, appropriate abstraction levels, and user experience. Avoid magic numbers or unclear parameters by using named constants or helper functions. Choose the right balance between generic and specific interfaces - use specific types when the use case is well-defined, but design for genericity when supporting multiple contexts or languages.
Consider the user experience by minimizing complex parameter construction. Instead of requiring users to manually construct complex objects, provide convenience methods or specializations.
Example from the discussions:
// Instead of magic numbers:
Symbolizer->symbolizeData(SymbolizerPath.str(), {Address, 0})
// Use explicit helpers:
static object::SectionedAddress getSectionedAddress(uint64_t Address) {
return {Address, object::SectionedAddress::UndefSection};
}
Symbolizer->symbolizeData(SymbolizerPath.str(), getSectionedAddress(Address))
// For return types, prefer specific over generic when appropriate:
std::pair<const NamedDecl *, const WarnUnusedResultAttr *> // specific
// instead of:
std::pair<const NamedDecl *, const Attr *> // generic
// Provide convenience builders to avoid complex parameter construction:
builder.create<linalg::MatmulTransposeAOp>(...) // specialized
// instead of requiring users to construct affine maps manually
When designing cross-language or cross-context APIs, consider using discriminator fields or configuration parameters rather than embedding language-specific types in generic structures.
Avoid unnecessary null checks when the called function already handles null cases, and use references instead of pointers when null values are guaranteed not to occur.
Many codebases contain redundant null checks that add complexity without providing safety benefits. This occurs in several patterns:
Double-checking optionals: When extracting values from optionals, avoid checking both the optional’s validity and the extracted value if the extraction method already handles empty cases.
Redundant pre-checks: Don’t check for null before calling functions that already return null for invalid inputs.
Use references for non-null guarantees: When a parameter is guaranteed to be non-null by preconditions, use references instead of pointers to make this contract explicit.
Examples:
// ❌ Redundant: AsCString() already returns nullptr for empty strings
auto mod_name = m_opaque_sp->GetObjectName();
if (!mod_name) {
return nullptr;
}
return mod_name.AsCString();
// ✅ Simplified: Let AsCString() handle the empty case
auto mod_name = m_opaque_sp->GetObjectName();
return mod_name.AsCString();
// ❌ Redundant optional checking
std::optional<mlir::FlatSymbolRefAttr> aliasee = op.getAliaseeAttr();
if (aliasee && *aliasee)
return matchAndRewriteAlias(op, *aliasee, ...);
// ✅ Simplified optional handling
if (std::optional<llvm::StringRef> aliasee = op.getAliasee())
return matchAndRewriteAlias(op, *aliasee, ...);
// ❌ Using pointer when null is impossible
int processSymbol(const Symbol *symbol) {
// symbol can't be NULL due to preconditions
}
// ✅ Use reference to express non-null contract
int processSymbol(const Symbol &symbol) {
// Contract is clear: symbol cannot be null
}
Document null safety assumptions in comments when the non-null guarantee isn’t obvious from the function signature, and add explicit null checks (like LIBC_CRASH_ON_NULLPTR) only where parameters should never be null but the type system cannot enforce this.
Ensure errors are properly propagated to callers rather than handled locally with exits or fatal errors. Library functions should return error information to allow callers to make appropriate decisions about error handling and recovery.
Key principles:
llvm::Expected instead of calling exit() or llvm::report_fatal_error()handleAllErrors() instead of just converting errors to strings with toString()Example of proper error propagation:
// Bad: Fatal error in library function
void parseDataAccessPerfTraces(StringRef File) {
auto BufferOrErr = MemoryBuffer::getFile(File);
if (auto EC = BufferOrErr.getError())
llvm::report_fatal_error(StringRef(Error)); // Don't do this
}
// Good: Return error to caller
std::error_code parseDataAccessPerfTraces(StringRef File) {
auto BufferOrErr = MemoryBuffer::getFile(File);
if (auto EC = BufferOrErr.getError())
return EC; // Let caller decide how to handle
// ... continue processing
return std::error_code{};
}
// Good: Handle multiple errors properly
if (!RSDOrErr) {
handleAllErrors(RSDOrErr.takeError(), [&](ErrorInfoBase &EIB) {
Ctx->emitError(EIB.message());
});
} // Instead of: toString(std::move(Err))
This approach enables better error recovery, testing, and allows different callers to handle errors appropriately for their context.
Tests should comprehensively cover all relevant scenarios, including return values, edge cases, different compilation modes, and abnormal conditions. This includes:
pthread_barrier_wait(&barrier), check the return value and assert expected behavior:
int result = LIBC_NAMESPACE::pthread_barrier_wait(&barrier);
if (result == PTHREAD_BARRIER_SERIAL_THREAD) {
serial_counter.fetch_add(1);
} else {
ASSERT_EQ(result, 0);
}
Edge Case Coverage: Add tests for abnormal scenarios like address capturing, incorrect function signatures, and boundary conditions that might not occur in normal usage but could reveal bugs.
// RUN: %clang_cc1 -fsycl-is-host -fsyntax-only -verify %s
// RUN: %clang_cc1 -fsycl-is-device -fsyntax-only -verify %s
Coverage Analysis: Use coverage tools to identify untested branches and remove or test unreachable code paths. Functions with many branches should have their coverage verified to ensure all paths are exercised.
constexpr, consteval, and other constructs that might interact with the code being tested.This approach helps catch bugs that might only manifest in specific scenarios and ensures robust, reliable code across different environments and use cases.
Environment files and variables must be loaded before creating any factories, services, or components that depend on them. This ensures that environment-based configuration propagates correctly throughout the application lifecycle.
Load environment files early in the initialization sequence, before creating factories or services that read environment variables. Additionally, clean up sensitive environment variables after reading to prevent unintended propagation to subprocesses.
Example of correct ordering:
// Load environment file first
load_env_variables_from_env_file(
flags.env_file,
log_level,
);
// Then create factory (which may read env vars)
let factory = create_factory()?;
let cli_options = factory.cli_options()?;
// Clean up sensitive env vars after reading
let control_sock = std::env::var("DENO_CONTROL_SOCK")?;
std::env::remove_var("DENO_CONTROL_SOCK"); // Prevent subprocess propagation
Avoid using .env() in CLI argument definitions since the environment file hasn’t been processed yet at argument parsing time. Instead, read environment variables after the environment file has been loaded and apply them to the appropriate configuration structures.
When validating network addresses and interfaces, properly handle special address values and use protocol-specific prefixes to avoid ambiguity. Network address validation should account for standard special addresses like INADDR_ANY (0.0.0.0) which allows the operating system to choose an appropriate interface, rather than treating them as invalid. For address parsing that supports multiple protocols, use explicit prefixes to disambiguate between different address types.
For multicast interface validation, allow 0.0.0.0 as a valid interface address:
// Instead of rejecting 0.0.0.0 as invalid
if !interface_addr.is_multicast() {
return Err(NetError::InvalidHostname(address));
}
// Allow INADDR_ANY for OS interface selection
if interface_addr == Ipv4Addr::new(0, 0, 0, 0) || interface_addr.is_multicast() {
// Valid interface specification
}
For address parsing supporting multiple protocols, require explicit prefixes:
// Use protocol prefixes to avoid parsing ambiguity
match address_str {
addr if addr.starts_with("tcp:") => parse_tcp_address(&addr[4..]),
addr if addr.starts_with("unix:") => parse_unix_address(&addr[5..]),
addr if addr.starts_with("vsock:") => parse_vsock_address(&addr[6..]),
_ => return Err("Address must specify protocol prefix"),
}
This approach prevents validation bugs where legitimate network configurations are incorrectly rejected and ensures consistent address parsing across different network protocols.
Always protect shared data that can be accessed by multiple threads using appropriate synchronization mechanisms. Identify potential race conditions where shared state could be modified concurrently and choose the right protection mechanism.
For simple data that needs atomic updates, use atomic operations:
// TODO: needs a mutex
// Better: use atomic for simple counters/flags
std::atomic<int> shared_counter{0};
For complex shared state or when you need to protect critical sections, use mutexes:
// Protect complex operations on shared data
std::mutex data_mutex;
void update_shared_state() {
std::lock_guard<std::mutex> lock(data_mutex);
// Critical section - modify shared data safely
}
Be especially careful with global variables that have external linkage, as they can be modified by other compilation units, breaking thread safety assumptions. Consider using static linkage or proper synchronization:
// Problematic: global linkage allows external modification
const int SHARED_CONSTANT = -1;
// Better: static linkage prevents external access
static const int SHARED_CONSTANT = -1;
// Or: use proper synchronization for truly shared data
When reviewing code, look for shared mutable state and verify it has appropriate protection against concurrent access.
Ensure algorithms handle boundary conditions and edge cases correctly by adding explicit validation for all input ranges. Special attention should be paid to index calculations, offset arithmetic, and memory management operations, as these are common sources of subtle bugs that only manifest in edge cases.
When implementing algorithms:
// Add safety check for numerical bounds
if let Some(ver) = ver {
// Ensure version index doesn't conflict with special flags
assert!(ver + 2 < VERSYM_HIDDEN);
elf::VERSYM_HIDDEN | (2 + ver as u16)
}
// Use correct length calculation (not offset + length)
(inner_prov.alloc_id(), offset.bytes(), len)
if tail {
// Detect unsupported cases
if has_indirect_args {
bug!("musttail call with indirect arguments is not supported");
}
// Ensure proper ordering of operations
bx.tail_call(fn_ty, fn_attrs, fn_abi, fn_ptr, llargs, self.funclet(fx), instance);
}
// Check storage liveness at every use location
if self.head_storage_to_check.contains(head) {
self.maybe_storage_dead.seek_after_primary_effect(location);
if self.maybe_storage_dead.get().contains(head) {
self.storage_to_remove.insert(head);
}
}
Thorough boundary condition validation prevents difficult-to-diagnose bugs and makes code more robust against unexpected inputs or state changes.
Documentation should be concise, focused, and maintainable. Follow these principles:
Good example:
/// A mutex guard for accessing protected data.
///
/// See [`Mutex`] for more usage examples.
#[must_use = "if unused the Mutex will immediately unlock"]
pub struct MutexGuard<'a, T: ?Sized + 'a> {
/// The underlying mutex reference
lock: &'a Mutex<T>,
}
Problematic example:
/// A mutex guard for accessing protected data.
/// This implements RAII pattern for mutex locking.
/// When dropped, it automatically unlocks the mutex.
///
/// # Examples
///
/// ```
/// // Long example duplicating documentation from Mutex
/// let mutex = Mutex::new(0);
/// let guard = mutex.lock();
/// *guard = 2;
/// // Many more lines...
/// ```
pub struct MutexGuard<'a, T: ?Sized + 'a> {
lock: &'a Mutex<T>, // Undocumented field
}
Functions should be documented with comprehensive JSDoc comments that cover:
Example:
/**
* Returns a diagnostic message for ECMAScript module syntax errors
* in CommonJS files under verbatimModuleSyntax.
*
* @param node - The AST node where the error occurred
* @returns A DiagnosticMessage explaining the error. If the file has a .cts or .cjs extension,
* a specific error message for CommonJS files is returned. Otherwise, a more general
* error message with suggestions for resolving the issue is returned.
* @throws {SyntaxError} If the node type is invalid for module syntax checking
* @deprecated Use getModuleDiagnostic() instead
*/
function getVerbatimModuleSyntaxErrorMessage(node: Node): DiagnosticMessage {
// Skip validation for non-module files to improve performance
const sourceFile = getSourceFileOfNode(node);
const fileName = sourceFile.fileName;
// ...
}
Always validate performance optimizations with benchmarks before implementation. Ensure measurements account for:
Example:
#[bench]
fn write_64bit_hex(bh: &mut Bencher) {
// Test common cases first
bh.iter(|| {
write!(black_box(discard), "{:x}", black_box(0_u64)).unwrap();
write!(black_box(discard), "{:x}", black_box(10000_i64)).unwrap();
// Test edge cases
write!(black_box(discard), "{:x}", black_box(-10000_i64)).unwrap();
});
}
Avoid premature optimization based on theoretical gains. For instance, reusing buffers might seem beneficial but benchmarks often show stack allocation is equally efficient:
// Prefer simple, clear code unless benchmarks show meaningful gains
let mut buf = NumBuffer::new(); // Creates temporary buffer on stack
format!("{}", number) // Let compiler optimize common case
Error messages should not only identify problems but also guide developers toward solutions. Include specific steps, alternatives, or configuration changes that can resolve the issue.
When designing error handling:
Example of a good error message:
// Instead of:
if (fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs])) {
return Diagnostics.ECMAScript_imports_and_exports_cannot_be_written_in_a_CommonJS_file_under_verbatimModuleSyntax;
}
// Prefer:
if (fileExtensionIsOneOf(fileName, [Extension.Cts, Extension.Cjs])) {
return Diagnostics.ECMAScript_imports_and_exports_cannot_be_written_in_a_CommonJS_file_under_verbatimModuleSyntax_Adjust_the_type_field_in_the_nearest_package_json_to_make_this_file_an_ECMAScript_module_or_adjust_your_verbatimModuleSyntax_module_and_moduleResolution_settings_in_TypeScript;
}
This approach reduces debugging time by helping developers understand not just what went wrong, but how to fix it. For complex configurations or language features, this guidance is especially valuable to both new and experienced developers.
Prefer using safe initialization methods like Default::default() over unsafe alternatives like MaybeUninit::uninit() or null values when initializing variables. This reduces the risk of undefined behavior and eliminates unnecessary unsafe blocks.
Example - Instead of:
let mut config_data: MaybeUninit<ConfigData> = MaybeUninit::zeroed();
// ... later ...
unsafe { config_data.assume_init() }
Prefer:
let config_data = ConfigData::default();
This approach:
Only use MaybeUninit or manual null handling when there are specific performance requirements or when dealing with FFI boundaries where default initialization is not possible.
Implement robust and type-safe configuration handling to avoid fragile hardcoding and improve maintainability. Key guidelines:
Example of improved configuration handling:
// Instead of this:
let docs = env::args_os().nth(1).expect("doc path should be first argument");
let docs = env::current_dir().unwrap().join(docs);
// Do this:
#[derive(Parser)]
struct Config {
docs: PathBuf,
#[clap(long)]
link_targets_dir: Vec<PathBuf>,
}
let config = Config::parse();
let docs = config.docs.canonicalize()?;
This approach:
Function names and their documentation must accurately reflect what the function actually does. When a function handles more data than its name suggests, either rename the function or split the functionality.
In the example below, formatDate() actually formats both date and time components, making the name misleading:
/**
* Formats the given date as a string in the form "YYYYMMdd".
*/
function formatDate(date: Date): string {
const year = date.getFullYear().toString().padStart(4, "0");
const month = (date.getMonth() + 1).toString().padStart(2, "0");
const day = date.getDate().toString().padStart(2, "0");
const hour = date.getHours().toString().padStart(2, "0");
const minute = date.getMinutes().toString().padStart(2, "0");
const seconds = date.getSeconds().toString().padStart(2, "0");
return year + month + day + hour + minute + seconds;
}
Better approaches:
formatDateTime() and update documentation to “YYYYMMddTHHMMSS”formatDate() but remove time componentsformatDate() and formatTime() functionsThis prevents confusion about what data the function processes and ensures the API is self-documenting.
Configure CI workflows to intelligently manage system resources through conditional execution rather than duplicating steps. Use matrix variables or job conditions to determine when resource-intensive operations should run.
For resource monitoring tasks (like disk usage checks), define a condition that prevents redundant execution:
- name: print disk usage
# Only run this step if the free_disk operation isn't executed
if: ${{ !matrix.free_disk }}
run: |
echo "disk usage:"
df -h
When configuring runners, explicitly document resource constraints and set appropriate flags that control resource management steps:
runners:
- &job-windows
os: windows-2025
free_disk: true # Explicitly mark jobs that need disk space freed
<<: *base-job
This approach keeps workflows maintainable by avoiding duplicative steps, makes resource constraints visible, and ensures consistent behavior across different execution environments.
Write comprehensive tests that cover all relevant cases and variations while maintaining proper organization and structure. Key practices:
library/coretests)Example:
// In library/coretests/your_module.rs
#[test]
fn test_feature_behavior() {
// Test primary functionality
let result = do_something();
assert_eq!(result.primary(), expected);
// Test analogous case
let alt_result = do_something_similar();
assert_eq!(alt_result.similar(), expected);
// Test backwards compatibility
assert_eq!(result.legacy_method(), old_expected);
assert_eq!(result.new_method(), new_expected);
}
// Separate file for failure cases
// In tests/ui/your_module_failures.rs
//@ build-fail
//@ ignore-pass
fn test_invalid_case() {
// Test expected failure
let result = invalid_operation();
//~^ ERROR expected error message
}
Choose names that clearly convey the semantic meaning and purpose of the identifier, rather than using clever or ambiguous names. Names should be explicit and descriptive enough that their purpose is immediately clear to readers.
Key guidelines:
Example:
// Less clear:
impl LintStoreMarker for Store { }
let id = trait_pred.self_ty();
// More clear:
impl DynLintStore for Store { }
let type_id = trait_pred.self_ty();
This approach makes code more maintainable and self-documenting by reducing cognitive load on readers. When multiple similar concepts are in scope, use more specific names to distinguish their roles.
When configuring project dependencies, be mindful of feature flags that may introduce unnecessary build dependencies or slow down compilation. For libraries like serde, separate the core functionality from derive macros to improve build parallelization:
# Preferred approach - separates core from proc macros
serde = "1.0.219"
serde_derive = "1.0.219"
# Instead of:
# serde = { version = "1.0.219", features = ["derive"] }
For tools and utilities that need to be lightweight, avoid heavy dependencies with proc-macro features when alternatives exist. Consider using builder APIs instead of derive-based approaches, or explicitly opt out of default features that aren’t needed. This practice helps maintain fast build times and reduces the dependency footprint of your project.
When implementing or documenting synchronization primitives, always clearly specify how locks behave under exceptional conditions, particularly during thread panics. This information is critical for developers to write robust concurrent code.
Key aspects to document:
For example, when documenting a mutex implementation, include explicit statements like:
/// If this thread panics while the lock is held, the lock will be released like normal.
///
/// # Example
///
/// ```rust
/// use std::thread;
/// use std::sync::{Arc, nonpoison::Mutex};
///
/// let mutex = Arc::new(Mutex::new(0u32));
/// let mut handles = Vec::new();
///
/// for n in 0..10 {
/// let m = Arc::clone(&mutex);
/// let handle = thread::spawn(move || {
/// let mut guard = m.lock();
/// *guard += 1;
/// panic!("panic from thread {n} {guard}")
/// });
/// handles.push(handle);
/// }
///
/// for h in handles {
/// h.join().unwrap_err(); // Threads panicked as expected
/// }
///
/// // The mutex can still be locked despite previous panics
/// println!("Finished, locked {} times", mutex.lock());
/// ```
///
Additionally, be explicit about concurrent operation limits and clearly distinguish between different types of memory operations (e.g., volatile vs. atomic) to prevent misuse in concurrent contexts.
Always protect against potential null or undefined values before attempting property access or method calls to prevent runtime errors. This applies especially to array access, method calls like .some() or .length, and property access on objects that might be null.
Bad practice:
// Might throw if entrypoints is undefined
if (some(entrypoints, e => project.toPath(e) === path)) { ... }
// Might throw "Cannot read property 'length' of undefined"
const length = text.length;
// Potential index out of bounds
while (sourceFile.statements[pos].end < statement.end) { ... }
// Creates keys with 'undefined' as string literal
const key = `${useOnlyExternalAliasing ? 0 : 1}|${firstRelevantLocation && getNodeId(firstRelevantLocation)}|${meaning}`;
Good practice:
// Guard with logical AND
if (entrypoints && some(entrypoints, e => project.toPath(e) === path)) { ... }
// Use optional chaining
const length = text?.length;
// Check array bounds
while (pos < sourceFile.statements.length && sourceFile.statements[pos].end < statement.end) { ... }
// Provide safe fallback value
const key = `${useOnlyExternalAliasing ? 0 : 1}|${firstRelevantLocation ? getNodeId(firstRelevantLocation) : 0}|${meaning}`;
Modern TypeScript provides several ways to handle nullable values safely: optional chaining (?.), nullish coalescing (??), and traditional guards (x !== undefined). Choose the approach that makes your code most readable while ensuring runtime safety.
When implementing or modifying parsing and validation algorithms, carefully consider both the correctness and user experience aspects:
Preserve structural ordering in data structures like ASTs when order impacts semantics. As noted in discussion #1, “In some cases, like in macro scoping, the order is actually important,” so field reordering can introduce subtle bugs that are difficult to track down.
Implement intelligent error recovery for mismatched delimiters by analyzing the context. For different mismatched delimiters scenarios, determine the most likely error:
// When suggesting missing delimiters, analyze the surrounding context
// BAD: Generic message for all cases
if 1 < 2 {
let _a = vec!]; // Just showing "mismatched closing delimiter"
}
// GOOD: Context-aware message
if 1 < 2 {
let _a = vec!]; // "mismatched closing delimiter, may be missing open `[`"
}
Avoid over-suggesting fixes when multiple interpretations are possible. As mentioned in discussion #8, for constructs like #![w,), the fix could be either [w,] or (w,) depending on context, so the diagnostic algorithm should avoid suggesting a specific solution when ambiguous.
Track related delimiter errors by collecting information about unmatched opening delimiters to provide more comprehensive diagnostics, as suggested in discussion #7.
These validation algorithms should prioritize correctness while providing meaningful guidance to developers when errors occur.
Choose variable, function, and parameter names that clearly communicate their purpose and avoid confusion. Names should be descriptive enough that their intent is obvious without requiring additional context or comments.
Key principles:
file_content instead of file when it contains file contents)file and path that could mean the same thingExamples of improvements:
// Poor: confusing parameter names
fn string_reproduces(file string, pattern string, command string, path string) bool
// Better: clear, distinct parameter names
fn string_reproduces(file_content string, pattern string, command string, file_path string) bool
// Poor: abbreviated, unclear method name
fn (mut c DiffContext[T]) gen_str() string
// Better: descriptive method name
fn (mut c DiffContext[T]) generate_patch() string
// Poor: misleading function name vs return type
fn (mut g Gen) get_enum_type_idx_from_fn_name(fn_name string) (string, ast.Type)
// Better: name matches return type
fn (mut g Gen) get_enum_type_from_fn_name(fn_name string) (string, ast.Type)
This approach reduces cognitive load for code readers and makes the codebase more maintainable by eliminating ambiguity about what variables and functions represent.
Create user-friendly error messages that guide developers toward solutions. Error messages should:
Example:
// Instead of:
early_dcx.early_warn("option `-o` has no space between flag name and value");
// Do this:
early_dcx.early_warn(
"option `-o` has no space between flag name and value, which can be confusing"
);
early_dcx.early_help(
format!("insert a space between `-o` and `{name}` if this is intentional: `-o {name}`")
);
The error message should clearly identify the issue, show the problematic value, and provide actionable guidance for resolution. Use help/note sections to separate the error description from suggestions and additional context.
Complex or duplicated logic should be extracted into well-named helper functions to improve code readability and maintainability. This applies to:
Example:
Instead of:
fn handle_options(early_dcx: &EarlyDiagCtxt, args: &[String]) -> Option<getopts::Matches> {
// ... other code ...
if let Some(name) = matches.opt_str("o")
&& let Some(suspect) = args.iter().find(|arg| arg.starts_with("-o") && *arg != "-o")
{
let confusables = ["optimize", "o0", "o1", "o2", "o3", "ofast", "og", "os", "oz"];
if let Some(confusable) = check_confusables(&suspect, &confusables) {
early_dcx.early_warn(
"option `-o` has no space between flag name and value, which can be confusing",
);
early_dcx.early_note(format!(
"option `-o {}` is applied instead of a flag named `o{}` to specify output filename `{}`",
name, name, name
));
if !confusable.is_empty() {
early_dcx.early_note(format!("Do you mean `{}`?", confusable));
}
}
}
}
Better:
fn handle_options(early_dcx: &EarlyDiagCtxt, args: &[String]) -> Option<getopts::Matches> {
// ... other code ...
warn_on_confusing_output_filename_flag(early_dcx, &matches, args);
}
fn warn_on_confusing_output_filename_flag(early_dcx: &EarlyDiagCtxt, matches: &Matches, args: &[String]) {
if let Some(name) = matches.opt_str("o")
&& let Some(suspect) = args.iter().find(|arg| arg.starts_with("-o") && *arg != "-o")
{
let confusables = ["optimize", "o0", "o1", "o2", "o3", "ofast", "og", "os", "oz"];
if let Some(confusable) = check_confusables(&suspect, &confusables) {
early_dcx.early_warn(
"option `-o` has no space between flag name and value, which can be confusing",
);
early_dcx.early_note(format!(
"option `-o {}` is applied instead of a flag named `o{}` to specify output filename `{}`",
name, name, name
));
if !confusable.is_empty() {
early_dcx.early_note(format!("Do you mean `{}`?", confusable));
}
}
}
}
This improves code by:
Cache results of expensive computations and order operations from cheapest to most expensive to minimize performance overhead. This includes storing function call results in local variables, extracting constants to avoid repeated allocations, using appropriate buffer sizes, and placing fast conditional checks before slower ones.
Key optimization strategies:
node.fkey(), expr.expr.str(), or c.g.fn_addr.keys() in local variables when used multiple timess.len instead of arbitrary numbers for initial buffer capacity to minimize reallocationsbytestr() instead of str() when you don’t need to empty the bufferExample of caching expensive operations:
// Before: expensive repeated calls
if !n.contains('.') && n !in c.g.fn_addr.keys() {
// ... later in code
if n !in c.g.fn_addr.keys() {
// keys() called again
}
}
// After: cache the expensive result
fn_addr_keys := c.g.fn_addr.keys()
if !n.contains('.') && n !in fn_addr_keys {
// ... later in code
if n !in fn_addr_keys {
// reuse cached result
}
}
Example of condition ordering:
// Before: expensive check first
if node.expr is ast.StringLiteral && node.field_name == 'str' && !f.pref.backend.is_js()
// After: cheap check first
if !f.pref.backend.is_js() && node.field_name == 'str' && node.expr is ast.StringLiteral
Use camelCase for all variables, parameters, methods, and functions in JavaScript/TypeScript code to maintain consistency with language conventions. Choose names that accurately describe the purpose and behavior of identifiers, and avoid naming collisions between different scopes.
Do this:
interface CSVParserOptions {
header?: boolean;
delimiter?: string;
trimWhitespace?: boolean; // camelCase instead of snake_case
dynamicTyping?: boolean; // camelCase instead of snake_case
}
function diagnose(
fixtureDir: string,
config: {
options?: Partial<ts.CompilerOptions>;
extraFiles?: Record<string, string>; // Clear, descriptive name that won't conflict
} = {},
) {
const tsconfig = config.options ?? {};
const extraFiles = config.extraFiles;
// ...
}
Don’t do this:
interface CSVParserOptions {
header?: boolean;
delimiter?: string;
trim_whitespace?: boolean; // Inconsistent with JS/TS conventions
dynamic_typing?: boolean; // Inconsistent with JS/TS conventions
}
function diagnose(
fixtureDir: string,
config: {
options?: Partial<ts.CompilerOptions>;
files?: Record<string, string>; // Could conflict with local variables
} = {},
) {
const tsconfig = config.options ?? {};
const files = config.files;
// Can lead to confusion with other 'files' variables in the function
}
Avoid unused identifiers, or clearly document why they’re being parsed but not used. Choose names that accurately reflect the behavior of functions rather than their implementation details to improve code readability and maintainability.
Maintain consistent naming conventions across the codebase:
All(), Get(), Run() instead of all(), get(), run())
```cpp
// Incorrect
static void all(const v8::FunctionCallbackInfo<v8::Value>& info);
static void get(const v8::FunctionCallbackInfo<v8::Value>& info);// Correct static void All(const v8::FunctionCallbackInfo<v8::Value>& info); static void Get(const v8::FunctionCallbackInfo<v8::Value>& info);
- Use snake_case for property names and variables
```cpp
// Incorrect
V(clientId_string, "clientId")
// Correct
V(client_id_string, "clientId")
k prefix for constants or follow existing convention in the file
```cpp
// Incorrect
static constexpr size_t reserveSizeAndAlign = …// Correct static constexpr size_t kReserveSizeAndAlign = … // or static constexpr size_t RESERVE_SIZE_AND_ALIGN = …
- Use PascalCase for concepts and templates that define types
```cpp
// Incorrect
template <typename T>
concept is_callable = ...
// Correct
template <typename T>
concept IsCallable = ...
Following these conventions improves code readability and maintains consistency throughout the codebase.
When working with environment variables for configuration:
Reuse existing environment variables rather than creating new ones. Leverage established variables like NODE_TEST_CONTEXT where possible to avoid fragmentation.
Extract only necessary environment variables instead of passing the entire environment. This improves security and makes dependencies explicit.
// Avoid:
proxyEnv: process.env.NODE_USE_ENV_PROXY ? process.env : undefined,
// Prefer:
proxyEnv: process.env.NODE_USE_ENV_PROXY ? {
HTTP_PROXY: process.env.HTTP_PROXY,
HTTPS_PROXY: process.env.HTTPS_PROXY,
NO_PROXY: process.env.NO_PROXY
} : undefined,
Check environment variables consistently and be mindful of truthiness. Remember that environment variables are strings or undefined, and plan accordingly.
Document new environment variables if you absolutely must create them, and ensure they follow established naming conventions for your project.
Identify code paths that execute frequently (loops, hot functions, repeated calls) and optimize them by avoiding redundant computations, expensive operations, and unnecessary allocations.
Key optimization strategies:
fs::rename() over fs::copy() + fs::remove_file()Vec::push() and join() instead of repeated format!() callsRegex instances in loopsExample of avoiding redundant calls:
// Before: calling node.is_named() twice
fn caller() {
if node.is_named() {
render_node_range(cursor, node.is_named()); // redundant call
}
}
// After: cache the result
fn caller() {
let is_named = node.is_named();
if is_named {
render_node_range(cursor, is_named);
}
}
Focus especially on optimizing code that processes large datasets, handles user input, or runs in tight loops where small inefficiencies compound into significant performance impacts.
Documentation must precisely reflect the current state of the codebase to prevent confusion and ensure developers can rely on it. Ensure that:
Example of correct documentation:
You can run the test suite either using `bun test <path>` or by using the wrapper script `bun node:test <path>`. The `bun node:test` command runs every test file in a separate instance of bun.exe, to prevent a crash in the test runner from stopping the entire suite.
Rather than referencing deleted packages or using imprecise terminology, this accurately describes the current testing capabilities and their behavior.
When optimizing application performance, be mindful of system resource constraints and use appropriate patterns to handle different scenarios efficiently:
// Inefficient for large files - may hit 2 GiB limit
fs.readFile('large-file.txt', (err, data) => {
// Process the entire file at once
});
// Better approach - process chunks incrementally
const stream = fs.createReadStream('large-file.txt');
stream.on('data', (chunk) => {
// Process each chunk as it arrives
});
Memory management: Monitor memory usage metrics to identify potential issues. Watch for signs like rising rss values while heapTotal remains stable, which may indicate memory fragmentation or leaks.
Buffering strategy: For performance-critical I/O operations, implement proper buffering with explicit flush control. Handle system resource constraints like EAGAIN errors gracefully with appropriate backpressure or fallback mechanisms.
Addressing these resource constraints proactively will help your application maintain consistent performance under various load conditions and system states.
Tests should be named to describe the expected behavior or outcome being verified, not the input parameters or implementation details. This makes tests more meaningful as documentation and easier to understand when they fail.
When writing test names:
Instead of:
test('help arg value config must be a string', () => {
// Test code
});
Prefer:
test('when help option receives non-string value, then throws type error', () => {
// Test code
});
Tests should also have exactly one expected outcome without conditional assertions. This ensures deterministic tests that clearly indicate what behavior is considered correct.
Avoid:
if (stderr) {
// One assertion path
} else {
// Different assertion path
}
Instead, be explicit about what the test expects:
strictEqual(stderr, '');
strictEqual(stdout, 'exports require module __filename __dirname\n');
When implementing iterative algorithms, ensure comprehensive handling of edge cases and boundary conditions. This includes:
Example - Before (problematic):
if (strings.endsWith(normalized_name, "/")) {
normalized_name = normalized_name[0 .. normalized_name.len - 1];
} else if (strings.endsWith(normalized_name, "\\")) {
normalized_name = normalized_name[0 .. normalized_name.len - 1];
}
Example - After (robust):
while (strings.endsWith(normalized_name, "/") or
strings.endsWith(normalized_name, "\\")) {
normalized_name = normalized_name[0 .. normalized_name.len - 1];
}
This approach prevents issues like:
When implementing iterative algorithms, always consider:
When extending APIs with new capabilities, carefully consider how changes to return values affect consumers. Avoid creating polymorphic return types based on input options as this reduces predictability and makes it difficult to programmatically detect supported features.
Instead, follow these patterns:
Symbol.dispose)mkdtemp → mkdtempDisposable)// AVOID: Polymorphic returns based on options
function createTempDir(prefix, { disposable = false } = {}) {
const path = makeTempDir(prefix);
return disposable ?
{ path, [Symbol.dispose]: () => removeTempDir(path) } :
path;
}
// BETTER: Separate, clearly named APIs
function createTempDir(prefix) {
return makeTempDir(prefix);
}
function createDisposableTempDir(prefix) {
const path = makeTempDir(prefix);
return {
path,
[Symbol.dispose]: () => removeTempDir(path)
};
}
When introducing new return types:
Error messages should be specific, actionable, and include context that helps developers understand and fix the issue. Use standard error codes that can be tested and suppressed rather than relying only on text messages. Create errors at the point of detection rather than inside callbacks to preserve useful stack traces.
When creating custom error messages for complex scenarios, include:
Example of an improved error message:
// Instead of:
throw new Error('Invalid module format');
// Prefer:
throw new ERR_AMBIGUOUS_MODULE_SYNTAX(
'Cannot determine intended module format because both `require()` and top-level ' +
'`await` are present. If the code is intended to be CommonJS, wrap `await` in an ' +
'async function. If the code is intended to be an ES module, replace `require()` ' +
'with `import`.'
);
// And when testing, check the code, not the message:
assert.throws(() => moduleOperation(), { code: 'ERR_AMBIGUOUS_MODULE_SYNTAX' });
For warnings that should be suppressible, always use a warning code:
process.emitWarning(
'Detected `fs.readFile()` to read a huge file in memory. Consider using ' +
'`fs.createReadStream()` instead to minimize memory overhead.',
'ERR_FS_FILE_TOO_LARGE'
);
When handling errors in async contexts, create the error object before entering the async callback to ensure it has a useful stack trace:
// Instead of:
if (options.signal?.aborted) {
return process.nextTick(() => callback(new AbortError()));
}
// Prefer:
if (options.signal?.aborted) {
const err = new AbortError();
return process.nextTick(() => callback(err));
}
When introducing new API features or changes, implement proper versioning to maintain stability and backward compatibility. Key practices:
Example:
// Good: New enum value added at end with experimental guard
typedef enum {
napi_uint32_array,
napi_float32_array,
napi_float64_array,
#ifdef NAPI_EXPERIMENTAL
napi_float16_array, // New experimental feature
#endif
} napi_typedarray_type;
// Good: Implementation with version check
if (env->module_api_version != NAPI_VERSION_EXPERIMENTAL) {
// Handle non-experimental version appropriately
}
This approach ensures API stability, enables proper testing between versions, and provides clear migration paths for API consumers.
Use compile-time configuration checks with cfg! macros instead of runtime environment variable parsing when the configuration decision can be determined at build time. This improves performance by eliminating runtime checks and makes the code’s behavior more predictable.
Prefer this:
if cfg!(target_family = "wasm") {
let sysroot = std::path::Path::new("bindings/rust/wasm-sysroot");
c_config.include(sysroot);
}
#[cfg(target_env = "msvc")]
c_config.flag("-utf-8");
#[cfg(feature = "std")]
use std::fs;
Instead of this:
if std::env::var("TARGET").unwrap() == "wasm32-unknown-unknown" {
let sysroot_dir = std::path::Path::new("bindings/rust/wasm-sysroot");
c_config.include(sysroot_dir);
}
Use cfg! for target platform checks, #[cfg(feature = "...")] for optional features, and #[cfg(target_env = "...")] for toolchain-specific behavior. Reserve runtime environment variable checks for user-configurable settings that must be determined at runtime, not build-time architectural decisions.
Maintain consistent naming conventions throughout the codebase to improve readability and reduce confusion. Adhere to these guidelines:
create for factory functions, which accurately conveys their purpose and aligns with existing patterns.
// Preferred
const counter = createCounter('api.calls', { service: 'web' });
const timer = createTimer('api.request.duration', { service: 'web' });
// Avoid
const counter = counter('api.calls', { service: 'web' });
// Preferred (instance method)
blockList.fromJSON(value)
// Avoid (incorrect capitalization for instance method)
BlockList.fromJSON(value)
Semantic specificity: Choose specific method names that prevent collisions and clearly communicate purpose. Avoid overly generic names like use() when more descriptive alternatives like useEventListener() or addDisposableEventListener() are clearer.
Domain-consistent terminology: Use consistent terminology across related APIs. For example, if most of your file system API uses “file descriptor” or “fd”, maintain that terminology throughout rather than using alternatives like “handle” for the same concept.
Consistent naming makes codebases easier to learn, navigate, and maintain while reducing cognitive load for developers.
Standardize testing by leveraging established patterns and helper functions throughout your test suite. Extract repetitive test setup into lifecycle hooks like beforeEach or shared fixtures. Use existing test helpers rather than duplicating logic, and organize tests logically by functionality.
For example, instead of repeating server setup in multiple tests:
test("test case 1", async () => {
using server = Bun.serve({
port: 0,
async fetch(req, res) { /* ... */ },
});
// test logic
});
test("test case 2", async () => {
using server = Bun.serve({
port: 0,
async fetch(req, res) { /* ... */ },
});
// test logic
});
Prefer using lifecycle hooks:
describe("server tests", () => {
let server;
beforeEach(() => {
server = Bun.serve({
port: 0,
async fetch(req, res) { /* ... */ },
});
});
afterEach(() => {
server.stop();
});
test("test case 1", async () => {
// test logic using server
});
test("test case 2", async () => {
// test logic using server
});
});
Similarly, use established helper functions (like fixtureURL, itBundled) for resource loading and specific testing scenarios. Group related tests into logical units and place tests in files that match their functionality. This approach reduces code duplication, improves maintainability, and makes tests easier to understand.
When implementing features that depend on specific compilation targets or modes, design configuration mechanisms that gracefully handle target availability differences. Provide clear detection methods for feature availability on the current target, and ensure attributes and builtins work consistently across different compilation contexts.
For heterogeneous compilations, distinguish between general feature availability and target-specific availability. Use __has_target_builtin for target-specific checks rather than __has_builtin when the actual code generation capability matters:
#ifdef __CUDA__
#if __has_target_builtin(__builtin_trap)
__builtin_trap();
#else
abort();
#endif
#endif
For attributes that span multiple compilation modes, allow them to be present across all relevant targets even if they only affect specific ones. This enables consistent semantic analysis and diagnostics while maintaining target-specific behavior. For example, allow sycl_external on both host and device compilations to support unified diagnostic checking.
When target validation is complex or depends on build-time configuration, prefer graceful degradation over strict validation that could fail based on build flags. Treat target specifications as authoritative at the IR level and only validate what can be reliably checked without external dependencies.
When dealing with potentially null or undefined values, use optional chaining (?.) and nullish coalescing (??) operators instead of verbose conditionals. These operators make code more readable, concise, and prevent null reference errors.
// Verbose and error-prone
if (object && object.property) {
value = object.property;
} else {
value = defaultValue;
}
// Concise and safe
value = object?.property ?? defaultValue;
In real-world code, these operators simplify common patterns:
// From discussion #1: Safely access property with fallback
innerOk(this?.ok ?? ok, args.length, ...args);
// From discussion #13: Conditional assignment with fallback
this.secureOptions = secureOptions || undefined;
// From discussion #15: Lazy loading with nullish assignment
inspect ??= require('util').inspect;
return inspect;
These operators are particularly valuable for:
Remember that optional chaining short-circuits when a reference is nullish, while nullish coalescing only falls back when the left side is specifically null or undefined (not other falsy values like 0 or '').
Always initialize class attributes with default values to prevent AttributeError exceptions when accessing potentially undefined values. Use defensive coding patterns:
__init__() for critical attributeshasattr()) before accessing attributes that might be undefinedExample:
def update(self):
# Initialize with defaults before try block
self.len = 0
self.cap = 0
self.ptr = None
self.elem_type = None
self.elem_size = 0
try:
self.ptr = self.value.GetChildMemberWithName('ptr')
self.len = self.value.GetChildMemberWithName('len').unsigned
self.cap = self.value.GetChildMemberWithName('cap').unsigned
self.elem_type = self.ptr.type.GetPointeeType()
self.elem_size = self.elem_type.size
except:
# Already have default values
pass
def get_child_at_index(self, index):
if index not in range(self.len):
return None
try:
if not hasattr(self, 'ptr') or not hasattr(self, 'elem_size') or not hasattr(self, 'elem_type'):
return None
return self.ptr.CreateChildAtOffset('[%d]' % index, index * self.elem_size, self.elem_type)
except:
return None
This pattern ensures your code is resilient to initialization failures and prevents null reference issues in dependent methods.
Ensure all related configuration files are updated consistently when making changes to dependencies, versions, or other configuration settings. When updating a dependency version in one file (e.g., package.json), ensure that all related files (e.g., lockfiles, version specifications) are also updated to maintain consistency. This prevents subtle integration issues and build failures.
Example:
// When updating package.json
{
"dependencies": {
"browser-ui-test": "0.21.1", // Updated from 0.20.6
// other dependencies...
}
}
// Also update related files like browser-ui-test.version to match
For build configurations, consider separating build artifacts from source code by copying configuration files to the build directory and running commands from there. This maintains source directory immutability during builds.
Ensure thread-safe access to shared resources while following proper resource management patterns. When implementing concurrent operations:
Example of proper thread-safe resource management:
// Instead of static instance
class NetworkResourceManager {
private:
std::mutex mutex_;
std::unordered_map<std::string, std::shared_ptr<Resource>> resources_;
public:
void Put(const std::string& url, std::unique_ptr<Resource> data) {
std::lock_guard<std::mutex> lock(mutex_);
resources_[url] = std::move(data);
}
// For parallel processing
void ProcessResource(const std::string& url) {
auto worker = std::make_unique<WorkerThread>([this, url]() {
// Thread-safe resource access
std::lock_guard<std::mutex> lock(mutex_);
if (auto it = resources_.find(url); it != resources_.end()) {
it->second->process();
}
});
worker->start();
}
};
This pattern ensures:
Error messages should provide specific context about the error and guide users toward solutions. When reporting syntax errors like mismatched delimiters, include both the location of the error and references to related code elements to help users quickly identify the problem.
For parser errors, prefer specific diagnostics over generic ones. Instead of vague messages like “mismatched closing delimiter”, use more helpful guidance such as “missing open ( for this delimiter” with clear indication of where both delimiters are located.
Example:
error: missing open `(` for a `)` delimiter
--> file.rs:3:1
|
LL | pub fn foo(x: i64) -> i64 {
| - the last matched opening delimiter
LL | x.abs)
| ^ missing open `(` for this delimiter
This approach helps users quickly understand the scope of the error and reduces debugging time by pinpointing both the error and its context. Remember that the goal of error messages is not just to report problems but to guide users toward solutions.
Always ensure errors are properly surfaced rather than silently ignored. Silent error handling can lead to confusing behavior where features fail without any indication of what went wrong, making debugging difficult or impossible.
There are several ways to properly handle errors instead of silently swallowing them:
// Good
try {
await this.findTestFiles();
} catch (error) {
this.outputChannel.appendLine(Test discovery failed: ${error.message});
}
2. Maintain important assertions that validate input requirements:
```typescript
// Bad
export function isReadableStreamLocked(stream) {
// $assert($isReadableStream(stream)); // Don't remove assertions!
}
// Good
export function isReadableStreamLocked(stream) {
$assert($isReadableStream(stream));
}
// Good
const initResult = handle.init(initParamsArray, pledgedSrcSize, writeState, processCallback);
if (initResult !== 0) {
throw new Error(Initialization failed with code ${initResult});
}
When errors are properly surfaced, developers can identify and fix issues more efficiently, leading to more robust and maintainable code.
---
## Respect existing configurations
<!-- source: oven-sh/bun | topic: Configurations | language: TypeScript | updated: 2025-07-10 -->
When integrating with external services or tools, design your configuration options to respect users' existing environment settings rather than overriding them with hardcoded defaults. This ensures your tool works seamlessly within various environments and avoids disrupting users' workflows.
Key practices:
1. Use environment-aware defaults that fall back to system conventions when no explicit value is provided
2. Apply configuration parameters consistently throughout the codebase
3. Use correct references to external resources
**Example 1:** For AWS CLI integration, omit the `--profile` flag when no profile is specified rather than hardcoding "default":
```typescript
// ❌ Bad: Hardcoding a default profile name
profile: Flags.string({
description: "The AWS CLI profile to use.",
multiple: false,
default: "default", // Forces "default" even if user has AWS_PROFILE set
})
// ✅ Good: Make profile optional and apply it conditionally
profile: Flags.string({
description: "The AWS CLI profile to use.",
multiple: false,
required: false,
})
// Then in code:
if (profile) {
// Apply profile parameter consistently to all AWS commands
this.#aws(["command", "--profile", profile]);
}
Example 2: Ensure external resource references are correct and complete:
// ❌ Bad: Incomplete GitHub issues URL
"bugs": "https://github.com/oven-sh/issues"
// ✅ Good: Complete URL to the repository's issues page
"bugs": "https://github.com/oven-sh/bun/issues"
Following these practices helps users integrate your tool with their existing environment without unexpected side effects or configuration conflicts.
Ensure test assertions correctly validate the intended behavior by:
// CORRECT: Properly wait for and validate promise resolution await expect(promise).resolves.toBe(expectedValue); // OR return expect(promise).resolves.toBe(expectedValue);
2. **Comparing appropriate values** when testing transformations:
```javascript
// PROBLEMATIC: Comparing decompressed output to compressed input
expect(decompressedBuffer).toEqual(compressedBuffer);
// CORRECT: Verifying the transformation worked as expected
expect(decompressedBuffer.toString()).toEqual(originalInputString);
Understanding your testing framework’s behavior is important - some frameworks may handle certain assertions differently (e.g., in some environments expect(...).resolves might implicitly await).
Tests should validate that functions behave as expected, not just that they run without errors. Always ask: “Is this assertion actually testing what I intend to test?”
When implementing file path matching or exclusion algorithms, use precise comparison techniques that respect directory boundaries rather than simple string operations. Naïve prefix matching using startsWith() can lead to unexpected behavior by accidentally matching unrelated paths that share prefixes.
For example, with an exclude pattern of 'foo', a simple implementation might exclude both 'foo/bar' (intended) and 'foobar' (likely unintended).
Instead, implement directory-aware comparisons:
// Incorrect - too inclusive
if (excludeGlobs?.some(([glob, p]) => glob.match(ent) || ent.startsWith(p)))
// Correct - respects directory boundaries
if (excludeGlobs?.some(([glob, p]) => glob.match(ent) || p === ent || ent.startsWith(p + path.sep)))
For complex pattern matching like glob implementations, consider both exact matches and proper directory hierarchy to ensure your algorithm behaves predictably across edge cases, especially when handling wildcards or path separators that may differ between operating systems.
Ensure all CI/CD workflow configuration files are validated for accuracy and consistency. Small errors in workflow files can prevent automation from triggering correctly or cause unexpected behavior. Pay special attention to:
Examples of common issues:
Incorrect path trigger (file extension mismatch):
on:
pull_request:
paths:
- ".github/workflows/bun-release-test.yml" # Wrong: actual file has .yaml extension
Correct path trigger:
on:
pull_request:
paths:
- ".github/workflows/bun-release-test.yaml" # Matches the actual file extension
Incorrect property name:
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
lables: "automation" # Wrong: 'lables' is a typo
Correct property name:
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3
with:
labels: "automation" # Correct property name
Consider implementing a validation step in your workflow development process to catch these issues early.
When multiple built-in methods can accomplish the same algorithmic task, prefer the single comprehensive method over combining multiple operations. This reduces computational overhead, simplifies code logic, and often provides better performance characteristics.
For example, instead of combining separate property retrieval methods:
// Less efficient - two separate operations
let allProperties = [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj),
];
// More efficient - single comprehensive method
let allProperties = Reflect.ownKeys(obj);
Similarly, use direct comparison methods when they handle edge cases automatically:
// Manual edge case handling
return NumberIsNaN(a) && NumberIsNaN(b) || a === b;
// Built-in handles all cases efficiently
return Object.is(a, b);
This approach improves algorithmic efficiency by leveraging optimized native implementations and reduces the cognitive load of understanding complex conditional logic.
Maintain consistent style patterns throughout the codebase to improve readability, reduce maintenance overhead, and prevent errors. Key areas to focus on:
const Installer = @This();
instead of:
pub const Installer = struct {
// Bad - duplicates in options list
GLOBAL_OPTIONS[LONG_OPTIONS]="--extension-order --jsx-factory --jsx-fragment --extension-order --jsx-factory --jsx-fragment"
// Good - no duplicates
GLOBAL_OPTIONS[LONG_OPTIONS]="--extension-order --jsx-factory --jsx-fragment"
// Bad - inconsistent namespace qualification
if (comptime Environment.debug_checks) {
// Good - consistent qualification
if (comptime bun.Environment.debug_checks) {
// Consider switch statements for multiple conditions
fn isValidRedirectStatus(status: u16) bool {
return switch (status) {
301, 302, 303, 307, 308 => true,
else => false,
};
}
Consistent style makes code easier to read, review, and maintain for the entire team.
Reuse memory with buffer pools for temporary allocations instead of creating new buffers each time. This reduces memory allocation overhead and improves performance, especially for operations that frequently need temporary storage.
// Instead of this:
var link_target_buf: bun.PathBuffer = undefined;
// Use this:
const link_target_buf = bun.PathBufferPool.get();
defer bun.PathBufferPool.put(link_target_buf);
Using memory pools for buffers that are only needed temporarily during a function call helps avoid expensive allocation and deallocation cycles. The pattern of getting from a pool and deferring the return ensures the memory is properly recycled without leaking, even if the function returns early through different code paths.
APIs should provide clean abstraction boundaries without exposing internal implementation details or creating unwanted dependencies between modules. When designing public interfaces, carefully consider what needs to be exposed versus what can remain internal.
Common violations include:
Instead, prefer:
pub use quinn::ConnectionError; rather than pub use quinn;Example of good abstraction:
// Instead of exposing complex internal resolver
pub fn op_require_fallback_resolve<T: NodeRequireLoader>(
state: &mut OpState,
request: String,
parent_filename: Option<String>,
) -> Result<String, RequireError>
// Expose simpler options-based API
#[derive(Debug, Default, Clone)]
pub struct ConditionOptions {
pub conditions: Vec<Cow<'static, str>>,
pub import_conditions_override: Option<Vec<Cow<'static, str>>>,
}
This approach reduces coupling, improves maintainability, and provides cleaner interfaces for API consumers.
Ensure appropriate permission checks are implemented before performing security-sensitive operations that access files, networks, or system resources. Different operation types require different permission validations, and the timing of these checks matters for security.
Key considerations:
Example implementation:
#[op2]
pub fn op_node_database_backup(
#[cppgc] source_db: &DatabaseSync,
#[string] path: String,
#[serde] options: Option<BackupOptions>,
) -> std::result::Result<(), SqliteError> {
// Add write permission checks here for the target path
// Since path can have different forms, check permissions accordingly
let src_conn_ref = source_db.conn.borrow();
// ... rest of implementation
}
Always verify that permission checks align with the security model of the operation and consider whether the check should occur at operation time or resource creation time.
Always use portable, environment-agnostic values in configuration files. Avoid hardcoded absolute paths, system-specific values, or mismatched feature flags. Instead:
Example - Instead of:
-Wl,-non_global_symbols_no_strip_list,/Users/username/project/src/symbols.txt
bun.Analytics.Features.lockfile_migration_from_package_lock += 1 # for pnpm migration
Use:
-Wl,-non_global_symbols_no_strip_list,${CMAKE_SOURCE_DIR}/src/symbols.txt
bun.Analytics.Features.lockfile_migration_from_pnpm_lock += 1 # specific to migration type
This ensures configurations work consistently across different environments and accurately reflect system behavior.
When designing APIs, carefully consider which elements of your implementation become part of the public interface. Avoid exposing internal types, naming patterns, and implementation details that could confuse API consumers or lock you into supporting implementation details as part of your contract.
For example, instead of exposing internal types in public modules:
// Avoid this:
declare module "net" {
type SocketHandleData = { self: Socket; req?: object };
// Makes it look like a standard Node.js type
}
// Prefer this:
// Option 1: Move to your own namespace
declare module "bun" {
namespace internal {
type SocketHandleData = { self: Socket; req?: object };
}
}
// Option 2: Inline the type where needed
interface Socket {
_handle: { self: Socket; req?: object };
}
When dependencies might be deprecated or change, consider vendoring your own implementation rather than relying on shared code that might evolve independently:
// Instead of depending on potentially changing shared implementations:
resultBindings.push(
` pub inline fn ${formatZigName(fn.name)}(...) {`,
` return bun.JSC.${callName}(...);`, // Depends on external implementation
` }`,
);
Ensure all error paths properly clean up resources and handle errors appropriately. This includes:
Example of proper resource cleanup:
// Bad - potential resource leak
const data = allocator.alloc(u8, size);
const result = processData(data); // May fail
if (result == .err) return error.ProcessFailed;
// Good - cleanup guaranteed on error
const data = try allocator.alloc(u8, size);
errdefer allocator.free(data);
const result = try processData(data);
// For multiple resources
const key = try allocator.dupe(u8, input_key);
errdefer allocator.free(key);
try map.put(allocator, key, value);
Common issues to watch for:
This practice helps prevent resource leaks and ensures robust error handling across all execution paths.
Always validate and sanitize all network request parameters to prevent injection attacks. This includes:
// Bad - No validation on URL source
fetch(url, { signal: controller.signal })
// Better - Restrict URLs to trusted origins
const urlObj = new URL(url);
if (!['localhost', '127.0.0.1', '::1'].includes(urlObj.hostname)) {
throw new Error('URL must be a loopback address for security reasons');
}
fetch(url, { signal: controller.signal })
\r and \n that could manipulate request boundaries.// Bad - No validation against control characters
let payload = `CONNECT ${requestHost}:${reqOptions.port} HTTP/1.1\r\n`;
if (auth) {
payload += `proxy-authorization: ${auth}\r\n`;
}
// Better - Validate parameters to prevent request smuggling
function validateNoControlChars(value, name) {
if (/[\r\n]/.test(value)) {
throw new Error(`${name} contains invalid control characters`);
}
return value;
}
let payload = `CONNECT ${validateNoControlChars(requestHost, 'requestHost')}:${validateNoControlChars(reqOptions.port, 'port')} HTTP/1.1\r\n`;
if (auth) {
payload += `proxy-authorization: ${validateNoControlChars(auth, 'auth')}\r\n`;
}
These practices help prevent server-side request forgery (SSRF), HTTP request smuggling, and other injection-based attacks.
Error messages should be specific, contextual, and include relevant information to aid debugging and user understanding. Avoid generic messages like “invalid syntax” in favor of descriptive explanations that specify what was expected, what was found, and include the problematic data when helpful.
Key practices:
return error('Unsupported data type ${T} to voidptr') instead of return error('Unsupport data type to voidptr')return type mismatch, it should be 'int', but it is instead 'string' rather than just return type mismatch, it should be 'int'return error('strconv.atoi: parsing "${s}": values cannot start or end with underscores') instead of return error('strconv.atoi: parsing "${s}": invalid syntax')return error('number exceeds 64 digits: ${actual_number}') to show what caused the issueExample transformation:
// Before: Generic and unhelpful
if ch > 9 {
return error('strconv.atoi: parsing "${s}": invalid syntax')
}
// After: Specific and informative
if ch > 9 {
return error('strconv.atoi: parsing "${s}": character `${s[i]}` is not a valid digit')
}
This approach significantly improves the debugging experience and helps users understand exactly what went wrong and how to fix it.
When instrumenting applications with metrics, choose the correct metric type based on what you’re measuring to ensure accurate and useful observability data:
Use monotonic (increase-only) counters for events or cumulative quantities to ensure compatibility with systems like Prometheus that expect strictly increasing values.
Use histograms or distributions for durations rather than simple counters, as durations represent a distribution of values.
Consider specialized gauge types for specific use cases, such as high-water mark gauges for tracking peak values:
// GOOD: Using a histogram/distribution for request durations
const requestDuration = createHistogram('api.request.duration.ms');
function handleRequest(req, res) {
const startTime = performance.now();
// Process request...
requestDuration.record(performance.now() - startTime);
}
// GOOD: Using a monotonic counter for counting events
const apiCalls = createCounter('api.calls.total');
function handleRequest(req, res) {
apiCalls.increment();
// Process request...
}
// GOOD: Using a high-water mark gauge for tracking peak memory usage
const memoryHighWaterMark = createHighWaterMarkGauge('memory.peak.bytes');
setInterval(() => {
memoryHighWaterMark.update(process.memoryUsage().heapUsed);
}, 1000);
Choosing the right metric type ensures that monitoring systems can correctly interpret and visualize your data, leading to more meaningful insights and better observability.
Choose clear, consistent, and accurate identifiers that precisely reflect behavior and follow established patterns. This applies to function names, method names, variable types, and enum values.
For functions and methods:
"fail" in IDLOperation::call to match the prototype table registration)getIndex instead of getDirectIndex when properties might be getters)isInt32AsAnyInt instead of isUInt32() when the method handles multiple input formatsFor types and constants:
int64_t over long long) for better portabilityutf16 to match Node.js’s napi_get_value_string_utf16)Consistent and precise naming reduces bugs from misinterpreting an identifier’s purpose, improves code readability, and makes integration with other libraries more intuitive.
When handling credentials or secrets in code, avoid passing them directly through command substitution or template literals in shell commands, as this can lead to security vulnerabilities. Instead, write sensitive data to temporary files with restricted permissions, use those files for operations requiring credentials, and ensure proper cleanup afterward.
Example:
// Insecure way - potential command injection or exposure risk
await Bun.$`bash -c 'age -d -i <(echo "$AGE_CORES_IDENTITY")' < ${cores} | tar -zxvC ${dir}`;
// Secure way - controlled lifecycle for sensitive data
const identityFile = join(dir, "identity.key");
await Bun.write(identityFile, process.env.AGE_CORES_IDENTITY);
await Bun.chmod(identityFile, 0o600); // Restrict file permissions
await Bun.$`age -d -i ${identityFile} < ${cores} | tar -zxvC ${dir}`;
await Bun.rm(identityFile); // Clean up the sensitive file
This approach prevents credential leakage through command history, process lists, or insecure file permissions, and ensures sensitive data is promptly removed after use.
Identify and optimize object allocation in performance-critical paths by applying appropriate allocation strategies based on usage patterns. For frequently accessed objects, consider reusing instances rather than creating new ones. For short-lived objects in hot code paths, evaluate whether the JIT can optimize allocations away or if explicit object pooling/caching is needed.
For example, instead of creating new handler instances for each channel:
// Inefficient - creates new instance per channel
channel.pipeline().addLast(new MyHandler());
// Efficient - reuses single instance across channels
private static final MyHandler SHARED_HANDLER = new MyHandler();
// Add @Sharable annotation to handler class
channel.pipeline().addLast(SHARED_HANDLER);
For frequent operations, consider thread-local caching when appropriate:
// Cache event objects per thread for high-frequency operations
private static final ThreadLocal<MyEvent> EVENT_CACHE =
ThreadLocal.withInitial(MyEvent::new);
public void onHighFrequencyOperation() {
MyEvent event = EVENT_CACHE.get();
event.reset(); // Prepare for reuse
// Use event...
}
When dealing with buffer management, ensure proper release and consider zero-copy approaches:
ByteBuf buffer = null;
try {
buffer = allocator.directBuffer(size);
// Use buffer...
} finally {
if (buffer != null) {
buffer.release(); // Ensure proper release
}
}
For frequently used resource handles like file descriptors, consider caching and reuse:
// Instead of creating new pipes for each operation
if (pipe == null) {
pipe = FileDescriptor.pipe();
}
Measure performance impact of allocation strategies with benchmarks before committing to complex optimization patterns, as JIT may already optimize simple allocation patterns in some cases.
In concurrent systems, it’s critical to release locks before performing operations that might trigger reentrancy or block the current thread, such as waking waiters. Holding locks while waking tasks can lead to deadlocks if the awakened code attempts to acquire the same lock.
This pattern applies to any concurrency primitive that maintains a list of waiters, such as semaphores, notification systems, or task schedulers.
// PROBLEMATIC: Holding lock while waking waiters
let mut lock = self.waiters.lock();
while let Some(waker) = lock.waiters.pop() {
waker.wake(); // Deadlock risk if woken task tries to acquire the same lock
}
Instead, collect the wakers to wake while holding the lock, then drop the lock before waking them:
let wakers_to_wake = {
let mut lock = self.waiters.lock();
std::mem::take(&mut lock.waiters) // Take ownership of waiters
// Lock is released at the end of this block
};
// Wake waiters after releasing the lock
for waker in wakers_to_wake {
waker.wake();
}
For batch operations, you can use a helper structure like WakeList to avoid waking too many waiters at once:
let mut waker_list = WakeList::new();
let mut lock = self.mutex.lock();
while let Some(waker) = process_next_waiter(&mut lock) {
waker_list.push(waker);
if !waker_list.can_push() {
// Wake a batch of waiters with the lock temporarily dropped
drop(lock);
waker_list.wake_all();
// Reacquire the lock to continue processing
lock = self.mutex.lock();
}
}
// Don't forget to wake remaining waiters
drop(lock);
waker_list.wake_all();
By following this pattern, you maintain system liveness and avoid deadlocks in concurrent code.
Function and method names should precisely describe their purpose and behavior. Choose names that explicitly communicate what the function does, including any special conditions or behaviors.
Key guidelines:
toJSON() instead of toJson() for JSON serialization)setupBindings() over setupInstance() when setting up bindings rather than an instance)maybeEmitDeprecationWarning or emitDeprecationWarningIfAlgoIsShake instead of just emitDeprecationWarning)enable()/disable() if that’s the established pattern for similar functionality)Example:
// ❌ Poor naming - ambiguous about purpose and behavior
function setup(instance, memory) {
// Implementation
}
// ✅ Better naming - clearly describes what's being set up
function setupBindings(instance, { memory = instance.exports.memory } = {}) {
// Implementation
}
// ❌ Poor naming - doesn't indicate conditional behavior
const emitDeprecationWarning = getDeprecationWarningEmitter();
// ✅ Better naming - indicates the warning is conditional
const maybeEmitDeprecationWarning = getDeprecationWarningEmitter();
Design cache systems so that higher-level components control when cache entries are invalidated or cleaned up, rather than having cache operations happen automatically at lower levels. This allows callers to make informed decisions about cache lifecycle based on their broader context and requirements.
Lower-level components should provide cache management methods but not automatically invoke them. Instead, let the calling code decide the optimal timing for cache operations based on request boundaries, configuration changes, or other application-specific events.
Example of problematic automatic cleanup:
// Don't do this - automatic cleanup removes caller control
.map_err(JsErrorBox::from_err)?;
self.parsed_source_cache.free(&specifier); // Automatic cleanup
Better approach - let caller control:
// Provide cleanup methods but let caller decide when to use them
impl CachedProvider {
pub fn clear_cache(&self) { /* cleanup logic */ }
}
// Caller decides optimal timing (e.g., after request completion)
provider.process_request()?;
provider.clear_cache(); // Controlled by caller
This separation of concerns allows for more flexible caching strategies, such as keeping long-lived services with short-lived caches, and enables smarter invalidation timing that considers the broader application context.
When code contains non-obvious parameter choices, conditional logic, or unexpected implementation changes, add comments explaining the reasoning behind these decisions. This helps other developers understand the context and prevents confusion during code reviews.
Key scenarios requiring explanation:
Example:
// Force dynamic loading to true because static analysis
// cannot determine module type at compile time
.load_asset(&specifier, true, requested_module_type)
if !skip_graph_roots_validation {
// Skip validation when called from dynamic imports where
// root validation has already been performed upstream
self.graph_roots_valid(graph, roots, allow_unknown_media_types)?;
}
The goal is to make the code self-documenting so that future maintainers can understand not just what the code does, but why specific decisions were made.
Ensure API implementations match established conventions and reference implementations to maintain compatibility and predictability. When implementing APIs that parallel existing systems (like Node.js APIs or REST services), follow their error codes, response formats, and naming conventions precisely.
For error handling, return the same error types as reference implementations:
// INCORRECT: Using mismatched error type
if (array_buffer.typed_array_type != .ArrayBuffer) {
return env.setLastError(.arraybuffer_expected);
}
// CORRECT: Matching Node.js convention
if (array_buffer.typed_array_type != .ArrayBuffer) {
return env.setLastError(.invalid_arg);
}
For memory management in API responses, use memory-safe methods:
// INCORRECT: Potential memory leak
deletedObject.put(globalObject, JSC.ZigString.static("Key"), bun.String.init(item.key).toJS(globalObject));
// CORRECT: Memory-safe alternative
deletedObject.put(globalObject, JSC.ZigString.static("Key"), bun.String.createUTF8ForJS(globalObject, item.key));
Consider API property naming and response structure consistency to ensure your implementation is compatible with expected conventions, while avoiding unintended behaviors and resource leaks.
Write code that clearly communicates intent by using appropriate naming and formatting techniques. Improve readability by:
// Prefer: private const string ValueTuplePrefix = “ValueTuple`”; if (valueType.Module == SystemModule && valueType.Name.StartsWith(ValueTuplePrefix, StringComparison.OrdinalIgnoreCase))
2. Using explicit types instead of `var` when the type isn't obvious:
```csharp
// Instead of:
var relativePathToSpec = GetFilteredFileSpecs(fileSpecs);
// Prefer:
Dictionary<string, FileSpec> relativePathToSpec = GetFilteredFileSpecs(fileSpecs);
Array.Empty<byte>() over raw literals:
```csharp
// Instead of:
SHA256.Create().ComputeHash([])// Prefer:
SHA256.Create().ComputeHash(Array.Empty
4. Using pattern matching for null checks:
```csharp
// Instead of:
if (m_getterMethod != null && m_getterMethod.IsVirtual)
// Prefer:
if (m_getterMethod is not null && m_getterMethod.IsVirtual)
// Prefer: bool taken = ((opcode is ILOpcode.brtrue or ILOpcode.brtrue_s) && val.Value != 0) || ((opcode is ILOpcode.brfalse or ILOpcode.brfalse_s) && val.Value == 0);
6. Using operators for more concise code when available:
```csharp
// Instead of:
Vector256<int> result = Vector256.And(Vector256.LoadUnsafe(ref left, i), Vector256.LoadUnsafe(ref right, i));
// Prefer:
Vector256<int> result = Vector256.LoadUnsafe(ref left, i) & Vector256.LoadUnsafe(ref right, i);
Always throw the most specific exception type appropriate for the error condition and include contextual information in the error message. This helps developers quickly identify and fix issues.
Key practices:
PlatformNotSupportedException instead of NotSupportedException)OperationCanceledException and TaskCanceledExceptionExample:
// Bad
throw new Exception("Operation failed");
// or
throw new CryptographicException("Unknown error (0xc100000d)");
// Good
throw new PlatformNotSupportedException(
SR.Format(SR.ProcessStartSingleFeatureNotSupported, nameof(feature)));
// or
await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
operation.ExecuteAsync(cancellationToken));
Always validate null or undefined values before using them to prevent crashes and undefined behavior. When implementing null-handling code, consider these principles:
?*Type in Zig) when a value might be null, rather than assuming non-nullability:// BAD - Compiler will optimize out this check
fn requestTermination(handle: *WebWorkerLifecycleHandle) void {
if (@intFromPtr(handle) == 0) return;
// ...
}
// GOOD - Explicitly mark as nullable
fn requestTermination(handle: ?*WebWorkerLifecycleHandle) void {
if (handle == null) return;
// ...
}
undefined unless they’re guaranteed to be assigned before use:// BAD - Creates potential undefined behavior
struct {
last_modified_buffer: [32]u8 = undefined,
}
// GOOD - Safe initialization
struct {
last_modified_buffer: [32]u8 = [_]u8{0} ** 32,
}
// BAD - Uninitialized fields
return bun.new(FileRoute, .{
.ref_count = .init(),
.server = opts.server,
.blob = blob,
.headers = headers,
.status_code = opts.status_code,
// Missing has_last_modified_header initialization
});
// GOOD - All fields explicitly initialized
return bun.new(FileRoute, .{
.ref_count = .init(),
.server = opts.server,
.blob = blob,
.headers = headers,
.status_code = opts.status_code,
.has_last_modified_header = headers.get("last-modified") != null,
.has_content_length_header = headers.get("content-length") != null,
});
// DANGEROUS - Will crash if MaybeLocal is empty
Local<T> ToLocalChecked() const {
return m_local;
}
// SAFE - Explicit check before dereferencing
bool ToLocal(Local<T>* out) const {
if (IsEmpty()) {
return false;
}
*out = m_local;
return true;
}
When implementing error handling, balance between propagation and recovery. Design error types to preserve context while providing users with meaningful ways to handle failure.
For error propagation:
std::error::Error trait, including source() method for error chainingResult<usize> over Result<()> when position information is needed)Send + Sync error types when errors might cross thread boundariesFor recovery paths:
// Poor: May panic unexpectedly and loses context
let pos = std.stream_position().unwrap();
// Better: Provides fallback and preserves error context
let pos = std.stream_position().unwrap_or(0);
// Poor: Error type lacks context and proper trait implementation
struct MyError(());
// Better: Full featured error with context preservation
struct MyError<T> {
inner: T,
cause: io::Error,
}
impl<T> std::error::Error for MyError<T> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.cause)
}
}
// Even in examples, show proper error handling
// Poor: Ignores errors
let _ = compress_data(reader).await;
// Better: Shows proper error handling
compress_data(reader).await?;
Tokio projects follow specific import conventions for consistency and readability. Adhere to these guidelines:
use statements for different modules// Incorrect
use std::{
future::Future,
os::unix::io::{AsRawFd, RawFd},
};
// Correct
use std::future::Future;
use std::os::unix::io::{AsRawFd, RawFd};
// Incorrect - safety comment is too far from unsafe block
let filled = read.filled().len();
// Safety: This is guaranteed by invariants...
unsafe { pin.rd.advance_mut(filled) };
// Correct
// Safety: This is guaranteed by invariants...
unsafe { pin.rd.advance_mut(read.filled().len()) };
Following consistent import style and proper safety comment placement improves code readability and maintainability across the project.
Select and implement synchronization primitives that match your concurrency requirements and avoid unsafe patterns. Consider the number of consumers, thread safety requirements, and proper initialization order.
Key guidelines:
std::env::set_var, std::env::remove_var) in multi-threaded programs as it can cause data races with C/C++ codebroadcast channels for multiple consumers, mpsc for single consumersExample of correct async flag implementation:
impl AsyncFlag {
pub fn raise(&self) {
self.0.add_permits(1); // Preserve semaphore state
}
pub async fn wait_raised(&self) {
drop(self.0.acquire().await); // Proper waiting mechanism
}
}
Avoid unsafe patterns like direct environment modification in multi-threaded contexts, and ensure synchronization primitives maintain proper ordering and state consistency throughout their lifecycle.
When implementing concurrent code that waits for conditions to be met, avoid busy-wait loops that continuously consume CPU resources. Busy-waiting wastes processing power, reduces overall system performance, and can increase power consumption.
Instead, use proper synchronization primitives or give up execution time:
// Bad practice - continuously consumes CPU
bool receivedSignal = false;
while (!receivedSignal) { }
// Better option 1 - use synchronization primitives
var signal = new ManualResetEventSlim(false);
signal.Wait(timeoutMs); // Efficiently waits without consuming CPU
// In signal handler: signal.Set();
// Better option 2 - at minimum, yield execution time
while (!receivedSignal)
{
Thread.Sleep(1); // Or Thread.Yield()
}
For more complex scenarios, consider higher-level synchronization constructs like SemaphoreSlim, AutoResetEvent, or async/await patterns with TaskCompletionSource. These approaches not only improve performance but also make your concurrent code more maintainable and less error-prone.
Write tests that are maintainable, self-documenting, and that promote good testing practices:
// Bad: Custom test logic with manual exception checking
bool canceled = false;
try {
await GetAsync(useVersion, testAsync, uri, cts.Token);
} catch (TaskCanceledException) {
canceled = true;
}
Assert.True(canceled);
// Good: Use built-in assertion methods
await Assert.ThrowsAsync<TaskCanceledException>(() =>
GetAsync(useVersion, testAsync, uri, cts.Token));
Create reusable test helpers for common operations instead of duplicating test code. This improves maintainability and readability while reducing the chance of inconsistencies.
Use modern test patterns: Replace legacy patterns like “return 100 == success” with proper Assert-based validation that clearly communicates test expectations and failures.
Remove commented-out or dead test code that doesn’t contribute to validation. Keep tests clean and focused on what’s actually being tested.
When designing APIs, prioritize flexibility, ergonomics, and intuitiveness to create better user experiences. APIs should accept the most general parameter types appropriate for the functionality, handle edge cases gracefully, and provide methods that align with user expectations.
Key practices:
impl Into<String> instead of String)Example of flexible parameter types:
// Instead of this:
pub fn name(&mut self, name: String) -> &mut Self {
// implementation
}
// Prefer this:
pub fn name(&mut self, name: impl Into<String>) -> &mut Self {
let name = name.into();
// implementation
}
Example of intuitive async API design:
// Instead of requiring separate calls:
pub async fn wait(&self) {
// implementation that just waits
}
pub fn get(&self) -> Option<&T> {
// implementation
}
// Consider this more ergonomic design:
pub async fn wait(&self) -> &T {
// implementation that returns the value directly
}
Example of providing inspection methods:
impl<T> Receiver<T> {
/// Checks if this receiver is finished.
///
/// Returns true if this receiver has been polled and yielded a result.
pub fn is_finished(&self) -> bool {
self.inner.is_none()
}
// Additional inspection methods to query other aspects of state
}
Use the right error handling mechanism for each scenario: exceptions for recoverable situations, assertions only for true programming bugs, and consistent error codes for API boundaries. Don’t assert unreachable for scenarios that could legitimately occur, such as in this example:
// Instead of this:
if (!BlockRange().TryGetUse(mask, &use))
{
unreached(); // Bad: this could legitimately occur
}
// Do this:
if (BlockRange().TryGetUse(mask, &use))
{
use.ReplaceWith(node);
}
else
{
node->SetUnusedValue(); // Gracefully handle the case
}
Always validate input values to prevent runtime errors like division-by-zero:
DWORD cacheSize = CLRConfig::GetConfigValue(CLRConfig::UNSUPPORTED_NativeToILOffsetCacheSize);
if (cacheSize < 1)
{
cacheSize = 1; // Ensure cache size is at least 1 to prevent division-by-zero
}
For API design, use standard error reporting mechanisms rather than boolean out parameters. When throwing exceptions, select appropriate exception types that reflect the nature of the error, such as PlatformNotSupportedException for unsupported hardware features instead of failing fast with generic errors.
Maintain consistent naming patterns and casing across all related code entities to ensure clarity and prevent errors. This includes:
Example:
// CORRECT: Consistent casing of 'checkJs' option
// In code:
compiler.options.checkJs = true;
// In documentation:
// "Use the 'checkJs' option to get errors from JavaScript files."
// CORRECT: Consistent naming across related entities
type Foo = number;
namespace Foo {
export type U = string;
}
export = Foo;
// When renaming 'Foo', ensure it's renamed in all three places
Inconsistent naming leads to confusion, makes code harder to maintain, and can cause runtime errors when references don’t match.
Select data structures based on expected collection size and usage patterns. For small collections (typically fewer than 30 elements), consider using ordered containers like std::map instead of hash-based ones like std::unordered_map. With small collections, the theoretical O(log n) vs O(1) lookup time difference becomes negligible in practice, while ordered containers avoid hash function computation overhead and potential collision handling.
// Instead of using unordered_map for small collections:
std::unordered_map<ModuleRequest, ModuleWrap*> module_requests;
// Consider using an ordered map:
std::map<ModuleRequest, ModuleWrap*> module_requests;
When working with hash-based containers, reuse precomputed hash values when available (like V8’s GetIdentityHash()) instead of recomputing hashes with std::hash. This avoids unnecessary computation and improves performance, especially for string data that might already have an identity hash from the JavaScript engine.
When writing tests that capture output (like UI tests, stderr, or stdout), ensure you normalize any variable content that might change between runs or versions to prevent fragile tests. This includes version numbers, debug information, instruction identifiers, and other implementation details that don’t affect the functionality being tested.
For UI tests, use normalization directives:
//@ normalize-stderr: "!(dbg|noundef) ![0-9]+" -> "!$1 !N"
//@ normalize-stderr: "%[0-9]+" -> "%X"
//@ normalize-stdout: "current rust version: [0-9]+.[0-9]+.[0-9]+" -> "current rust version: #.#.#"
For complex output with potentially changing instruction ordering, consider using run-make tests with FileCheck instead of UI tests:
// In your run-make test
run(|config| {
let filecheck = llvm_filecheck();
filecheck
.arg("--check-prefix=CHECK")
.arg("--input-file=output.txt")
.arg("expected.txt");
});
This approach ensures tests remain stable across version updates and implementation changes, reducing maintenance overhead and preventing false test failures.
Strive for clarity by simplifying code expressions and reducing unnecessary complexity. Complex or verbose expressions decrease readability and increase the chance of errors during maintenance.
Key simplification practices:
// Simplified: PrecodeMachineDescriptor::Init(&g_cdacPlatformMetadata.precode);
2. **Use appropriate types** - Prefer language-native types for internal details:
```cpp
// Less appropriate for internal usage:
Volatile<BOOL> g_GCBridgeActive = FALSE;
// Better:
Volatile<bool> g_GCBridgeActive = false;
// Flattened and clearer: bool doInterpret = false; if ((g_interpModule != NULL) && (methodInfo->scope == g_interpModule)) doInterpret = true;
4. **Use parentheses in logical expressions** per coding guidelines:
```cpp
// Incorrect:
if (ins == INS_rcl_N || ins == INS_rcr_N || ins == INS_rol_N || ins == INS_ror_N)
// Correct:
if ((ins == INS_rcl_N) || (ins == INS_rcr_N) || (ins == INS_rol_N) || (ins == INS_ror_N))
// Extract to helper: emitMoveIfZeroImmediate(retReg, op1->GetRegNum(), attr);
6. **Collapse related conditionals** into helper functions:
```cpp
// Verbose:
if (IsAVXVNNIInstruction(ins) || IsAVXVNNIINT8Instruction(ins) || IsAVXVNNIINT16Instruction(ins))
// Better:
if (IsAvxVnniFamilyInstruction(ins))
Simpler code is easier to read, test, and maintain.
When implementing algorithms that operate on complex data structures (trees, graphs, dominator structures), abstract the traversal mechanism from the operation performed at each node. Use visitor patterns or callbacks to separate concerns, making algorithms more maintainable and reusable.
For example, instead of directly traversing a data structure and performing operations:
// Tightly coupled approach
void ScaleLoopBlocks(BasicBlock* begBlk, BasicBlock* endBlk)
{
for (BasicBlock* const curBlk : BasicBlockRangeList(begBlk, endBlk))
{
// Direct operations on curBlk mixed with traversal logic
if (curBlk->hasProfileWeight()) continue;
if (curBlk->isRunRarely()) continue;
if (!m_reachabilitySets->GetDfsTree()->Contains(curBlk)) continue;
// More operations...
}
}
Abstract the traversal pattern to separate it from node operations:
// Decoupled approach
void ScaleLoopBlocks(FlowGraphNaturalLoop* loop)
{
loop->VisitLoopBlocks([&](BasicBlock* curBlk) -> BasicBlockVisit {
if (curBlk->hasProfileWeight()) return BasicBlockVisit::Continue;
if (curBlk->isRunRarely()) return BasicBlockVisit::Continue;
// Operations on curBlk without traversal concerns
return BasicBlockVisit::Continue;
});
}
This approach offers several benefits:
Avoid recomputing expensive operations by caching results when they will be used multiple times. This applies to method calls, property access, and static property lookups. The performance impact of repeated calculations can be significant, especially in hot code paths.
Consider these approaches:
// Instead of this:
if (type.GetClassLayout().Kind != MetadataLayoutKind.Auto) { ... }
// And later:
return type.GetClassLayout().Kind switch { ... }
// Do this:
var layout = type.GetClassLayout();
if (layout.Kind != MetadataLayoutKind.Auto) { ... }
// And later:
return layout.Kind switch { ... }
// Instead of this:
if (IsHttp3Supported() && GlobalHttpSettings.SocketsHttpHandler.AllowHttp3 && _http3Enabled) { ... }
// And later again:
if (GlobalHttpSettings.SocketsHttpHandler.AllowHttp3) { ... }
// Do this:
bool allowHttp3 = GlobalHttpSettings.SocketsHttpHandler.AllowHttp3;
if (IsHttp3Supported() && allowHttp3 && _http3Enabled) { ... }
// And later:
if (allowHttp3) { ... }
// Instead of this:
symbolRemapping = new Dictionary<ISymbolNode, ISymbolNode>();
// Do this:
symbolRemapping = new Dictionary<ISymbolNode, ISymbolNode>(
(int)(1.05 * (previousSymbolRemapping?.Count ?? 0)));
Caching computed values not only improves performance but also makes code more readable by reducing duplication and making dependencies explicit.
Use defensive coding practices to prevent null reference exceptions by properly handling potentially null values throughout your code.
// PREFER: Proper instantiation
ArrayBuilder
2. **Capture and check results** of methods that might return null before using them:
```csharp
// AVOID: Potential NullReferenceException if ReadLine() returns null
while (!remoteHandle.Process.StandardOutput.ReadLine().EndsWith(message))
{
Thread.Sleep(20);
}
// PREFER: Capture and check for null
string? line;
while ((line = remoteHandle.Process.StandardOutput.ReadLine()) != null &&
!line.EndsWith(message))
{
Thread.Sleep(20);
}
// PREFER: Properly handle null context
return SignData(new ReadOnlySpan
4. **Use pattern matching** for more readable and concise null checks:
```csharp
// AVOID: Traditional null check with equality operator
if (setMethod == null || !setMethod.IsPublic)
// PREFER: Modern pattern matching
if (setMethod is null || !setMethod.IsPublic)
// PREFER: Combine related checks if (changeToken is not (null or NullChangeToken))
When adding validation in public APIs, favor the built-in helpers like `ArgumentNullException.ThrowIfNull()` over custom throw helpers for better readability and standardization.
---
## Always await promises
<!-- source: oven-sh/bun | topic: Concurrency | language: TypeScript | updated: 2025-07-01 -->
Consistently use the `await` keyword when working with Promise-returning functions to ensure proper execution flow and prevent race conditions. Missing awaits can lead to unexpected behavior where code continues execution without waiting for asynchronous operations to complete.
Common issues to watch for:
1. Built-in async functions like `Bun.sleep()` or `fetch()`
2. Custom async functions that return Promises
3. Promise-returning methods like `sink.end()` or `server.stop()`
4. Async operations within loops
```javascript
// Incorrect: Function continues executing immediately
async function processWithSleep() {
Bun.sleep(1000); // Missing await!
return new Response(html);
}
// Correct: Function properly pauses execution
async function processWithSleep() {
await Bun.sleep(1000);
return new Response(html);
}
// Incorrect: Could create race conditions
async function performOperation() {
const res = await fetch(server.url);
server.stop(); // Server might stop before response is processed
expect(await res.text()).toContain("expected content");
}
// Correct: Ensures operations complete before cleanup
async function performOperation() {
const res = await fetch(server.url);
expect(await res.text()).toContain("expected content");
await server.stop();
}
In loops with async operations, ensure each iteration properly awaits its results:
// Incorrect: Results might not be processed in order
for await (const params of paramGetter) {
callRouteGenerator(type, i, layouts, pageModule, params);
}
// Correct: Each iteration waits for completion
for await (const params of paramGetter) {
await callRouteGenerator(type, i, layouts, pageModule, params);
}
Names should clearly convey purpose and meaning. Parameter, variable, and method names should be self-explanatory and accurately reflect their behavior and intent. Rename elements when their original name no longer captures their purpose or could lead to confusion.
For example, if a parameter indicates whether side effects can be ignored:
// Less clear
bool IsRedundantMov(instruction ins, format fmt, size_t size, operand dst, operand src, bool canSkip)
// More clear
bool IsRedundantMov(instruction ins, format fmt, size_t size, operand dst, operand src, bool canIgnoreSideEffects)
Similarly, ensure that error messages and user-facing text use precise, accurate terminology that reflects current platform realities (like changing “not a managed .dll or .exe” to “not a managed .dll” when managed .exe files aren’t relevant in modern .NET).
When variables are used across different scopes (like in lambdas), be especially attentive to naming clarity to avoid ambiguity about which variables are being referenced.
When working with shared data in multithreaded environments, memory ordering is critical to prevent race conditions and ensure thread safety:
// INCORRECT: Creates a race window where incorrect value is visible
*ppvRetAddrLocation = (void*)pfnHijackFunction;
#if defined(TARGET_ARM64)
*ppvRetAddrLocation = PacSignPtr(*ppvRetAddrLocation);
#endif
// CORRECT: Prepare final value before publishing
void* pvHijackAddr = (void*)pfnHijackFunction;
#if defined(TARGET_ARM64)
pvHijackAddr = PacSignPtr(pvHijackAddr);
#endif
*ppvRetAddrLocation = pvHijackAddr;
InterlockedCompareExchange(destination, exchange, comparand):// INCORRECT: Arguments in wrong order
if (versionStart != InterlockedCompareExchange(&s_stackWalkNativeToILCacheVersion, versionStart, versionStart | 1))
// CORRECT: Proper argument order
if (versionStart != InterlockedCompareExchange(&s_stackWalkNativeToILCacheVersion, versionStart | 1, versionStart))
// INCORRECT: May be reordered
s_stackWalkCacheSize = cacheSize;
s_stackWalkCache = newCache; // Other threads might see the pointer before size is set
// CORRECT: Memory barrier ensures proper ordering
VolatileStore(&s_stackWalkCacheSize, cacheSize);
s_stackWalkCache = newCache;
Proper memory ordering is essential for preventing subtle multithreading bugs that may only appear under high load or on specific hardware architectures.
When configuring build and test processes in CI/CD pipelines, ensure proper dependency chains with clear inputs and outputs to avoid unnecessary execution steps and cross-platform failures. Use techniques like:
Explicit dependencies between build targets - Define clear dependency relationships to ensure components are built in the correct order
Up-to-date checks - Implement input/output dependency tracking to skip steps when source files haven’t changed
Cross-platform validation - Verify that file references and environment variables work across all target platforms and architectures
For example, when setting up build targets in MSBuild:
<Target Name="ExecuteGenerateTests"
Inputs="$(MSBuildAllProjects);$(SourceFiles)"
Outputs="$(GeneratedTestListFile)"
DependsOnTargets="BuildPrerequisites">
<!-- Command that generates test files -->
</Target>
<Target Name="CompileGeneratedTests"
BeforeTargets="BeforeCompile;CoreCompile"
DependsOnTargets="ExecuteGenerateTests">
<!-- Include generated test files in compilation -->
</Target>
This approach ensures that tests are only regenerated when source files change, dependencies are built in the correct order, and builds work consistently across different platforms, resulting in faster and more reliable CI/CD pipelines.
When implementing or modifying APIs, carefully preserve the historical behavior and semantic contracts of existing interfaces, even when those behaviors seem counterintuitive. This includes maintaining consistent exception throwing patterns and honoring the distinct purposes of method overloads.
For example, when implementing overloaded methods:
// Different overloads may have subtly different semantics by design
// This overload conditionally includes entries based on trimTarget being marked
public void AddExternalTypeMapEntry(string name, Type target, Type trimTarget) {
// Only include if trimTarget is marked
if (IsTrimTargetMarked(trimTarget)) {
RecordTypeMapEntry(name, target);
}
}
// While this overload is meant as an "escape hatch" with unconditional behavior
public void AddExternalTypeMapEntry(string name, Type target) {
// Always include this entry regardless of other conditions
RecordTypeMapEntry(name, target);
}
Similarly, maintain expected exception behaviors. If an API historically throws for certain inputs (like Sign throwing for NaN values), preserve this pattern even if it seems inconsistent with similar methods. These behaviors form part of the API’s contract that users may depend on.
Always use constant-time comparison methods when validating cryptographic values to prevent timing side-channel attacks. Operations like comparing authentication tags, MACs, hashes, or any security-sensitive values should use dedicated APIs such as CryptographicOperations.FixedTimeEquals() rather than standard equality operators or methods.
Example:
// INSECURE: Vulnerable to timing attacks
if (header != 0xA65959A6UL)
throw new CryptographicException();
// SECURE: Use constant-time comparison
uint err = (uint)(header) ^ 0xA65959A6U;
// Aggregate other validation results with bitwise OR
err |= pad & ~0x7;
// ...
if (err != 0)
throw new CryptographicException();
For larger data structures or byte sequences:
// INSECURE: String equality is not constant-time
if (computedTag == expectedTag)
return true;
// SECURE: Use crypto-specific APIs
return CryptographicOperations.FixedTimeEquals(
computedTag.AsSpan(),
expectedTag.AsSpan());
This practice is especially important for operations that verify authentication tags, decrypt ciphertexts, or validate signatures, where timing differences could reveal information about secret values to attackers.
Use organized structural patterns for conditional compilation to improve code maintainability and readability. Avoid applying #[cfg] attributes directly to function parameters - instead wrap conditionally available data in structs that can be zero-sized when features are disabled:
// Instead of this:
fn function(
param1: Type1,
#[cfg(some_feature)] param2: Type2,
) {
// ...
}
// Use this:
struct ConditionalParams {
#[cfg(some_feature)]
param2: Type2,
}
fn function(
param1: Type1,
conditional: ConditionalParams,
) {
// ...
}
For functions with different implementations based on configuration, use macro blocks to separate complete implementations rather than conditionally compiling within function bodies:
cfg_feature! {
fn my_function() {
// Implementation when feature is enabled
}
}
cfg_not_feature! {
fn my_function() {
// Implementation when feature is disabled
}
}
When stabilizing features gradually, split implementations into stable and unstable parts to avoid duplication and potential divergence. Group all related unstable functions within a single configuration block. Prefer established configuration macros like cfg_coop over direct feature checks when they more accurately represent the conditions for enabling functionality.
When working with feature flags and conditional compilation, prefer structural approaches over scattered configuration attributes throughout your code. This creates more maintainable code and reduces the chance of bugs when features are added or removed.
For conditionally included parameters:
// AVOID: Using cfg directly on function parameters
fn spawn(
&mut self,
task: T,
scheduler: S,
id: super::Id,
#[cfg(tokio_unstable)] spawned_at: &'static Location<'static>,
) { /* ... */ }
// PREFER: Wrapping conditional data in a struct
struct SpawnLocation {
#[cfg(tokio_unstable)]
location: Option<&'static Location<'static>>,
// The struct is effectively zero-sized when the feature is disabled
}
fn spawn(
&mut self,
task: T,
scheduler: S,
id: super::Id,
location: SpawnLocation,
) { /* ... */ }
For platform-specific implementations:
// AVOID: Scattered cfg blocks within functions
fn now() {
#[cfg(target_family = "wasm")]
{ return None; }
#[cfg(not(target_family = "wasm"))]
{ return Some(Instant::now()); }
}
// PREFER: Abstraction functions that handle conditional logic
fn now() -> Option<Instant> {
if cfg!(target_family = "wasm") { None } else { Some(Instant::now()) }
}
For feature-gated functionality, prefer separate function implementations over conditionally compiled code blocks:
// AVOID: Mixed conditional blocks within function bodies
fn submit(&mut self, worker: &WorkerMetrics, mean_poll_time: u64) {
#[cfg(tokio_unstable)]
{
// unstable metrics code...
}
// stable metrics code...
}
// PREFER: Separate implementations with a common pattern
cfg_unstable_metrics! {
fn submit_unstable(&mut self, worker: &WorkerMetrics, mean_poll_time: u64) {
// unstable metrics code...
}
}
fn submit(&mut self, worker: &WorkerMetrics, mean_poll_time: u64) {
// stable metrics code...
#[cfg(tokio_unstable)]
self.submit_unstable(worker, mean_poll_time);
}
Consider creating specialized macros for common conditional patterns to standardize your approach:
// For metrics that have both stable and unstable implementations
macro_rules! cfg_metrics_impl {
($name:ident, stable: $stable_impl:block, unstable: $unstable_impl:block) => {
cfg_unstable_metrics! {
fn $name(&mut self) $unstable_impl
}
cfg_not_unstable_metrics! {
fn $name(&mut self) $stable_impl
}
}
}
These structural approaches make conditional code easier to maintain, test, and understand.
Always use standard null-checking patterns like CHECK_NOT_NULL for pointer validation instead of manual null checks. This ensures consistent error handling and improves code readability. For functions that may return null objects, consider using wrapper types like MaybeLocal to properly propagate null states.
Example:
// Incorrect - manual null check
if (network_resource_manager_ == nullptr) {
return protocol::DispatchResponse::ServerError("...");
}
// Correct - using standard macro
CHECK_NOT_NULL(network_resource_manager_);
// Correct - using MaybeLocal for nullable returns
MaybeLocal<Object> CreateObject(Isolate* isolate) {
if (some_condition) return MaybeLocal<Object>(); // Return empty handle
return Object::New(isolate);
}
Ensure consistent formatting and organization throughout the codebase to improve readability and maintainability. This includes:
Examples:
Instead of inconsistent spacing in macro definitions:
#define s390_trap2(code) S390_E(code, 0x01ff)
#define s390_vab(c , v1, v2, v3) S390_VRRc(c, 0xe7f3, v1, v2, v3, 0, 0, 0)
Use consistent spacing:
#define s390_trap2(code) S390_E(code, 0x01ff)
#define s390_vab(c, v1, v2, v3) S390_VRRc(c, 0xe7f3, v1, v2, v3, 0, 0, 0)
For ordered lists like package sources, maintain logical ordering:
<add key="darc-pub-dotnet-emsdk-b567cdb-1" value="..." />
<add key="darc-pub-dotnet-emsdk-b567cdb-2" value="..." />
When formatting rules must be violated, document the exception:
<!-- SA1001: Commas should not be preceded by a whitespace; needed due to ifdef -->
<NoWarn>$(NoWarn);SA1001</NoWarn>
For grouping related elements, keep them together:
#define FIRST_AVXVNNIINT8_INSTRUCTION INS_vpdpwsud
// Group all AVXVNNI instructions together rather than spreading them
// throughout different sections
Always validate parameters and initialize variables before access to prevent null dereference and undefined behavior. When handling arrays or pointers, require and validate length parameters. For conditionally initialized variables, ensure all code paths explicitly set values.
Example:
// Unsafe: No length validation before indexing
static bool IsPathNotFullyQualified(WCHAR* path)
{
// Potential null dereference or buffer overflow
if (path[0] == VOLUME_SEPARATOR_CHAR) return false;
}
// Safe: Length parameter and validation
static bool IsPathNotFullyQualified(WCHAR* path, size_t length)
{
// Validate before accessing
if (path == NULL || length < 1) return true;
if (path[0] == VOLUME_SEPARATOR_CHAR) return false;
}
// For conditional initialization, always set values in all branches:
if (!bitmap_only)
interface_offsets_full[i] = gklass->interface_offsets_packed[i];
else
interface_offsets_full[i] = 0; // Prevent uninitialized value use
When modifying control flow to prevent null pointer dereferences, add explanatory comments to clarify the safety measures for future maintainers.
When implementing resource cleanup logic, especially disposers for explicit resource management, always design for resilience during error conditions:
SuppressedErrorclass MyDisposableResource {
#disposed = false;
dispose() {
// ✅ Idempotent - safe to call multiple times
if (this.#disposed) return;
this.#disposed = true;
try {
// Cleanup logic here
console.log('Resource cleaned up');
} catch (error) {
// ✅ Avoid throwing from disposers
console.error('Error during disposal:', error);
// Don't re-throw - this would mask any pending exception
}
}
[Symbol.dispose]() {
// ✅ Delegate to explicit disposal method
this.dispose();
}
}
This pattern ensures resources are cleaned up reliably even during error conditions. By making disposers idempotent and error-safe, you prevent cascading failures where cleanup code compounds the original problem. It’s especially important in using/await using blocks where disposers are called automatically during both normal and exceptional exits, with no information about pending exceptions.
Embrace modern C++20 features throughout the codebase to improve code readability, maintainability, and performance. Specifically:
// Preferred
namespace node::inspector::protocol {
// ...
}
// Instead of
namespace node {
namespace inspector {
namespace protocol {
// ...
}
}
}
std::string_view over const std::string& for parameters that don’t need ownership:
// Preferred
const auto function = [](std::string_view path) {
// Use path directly without copying
};
// Instead of
const auto function = [](const std::string& path) {
// Potentially causes unnecessary copies
};
enum class for better type safety and scope control:
// Preferred
enum class Mode { Shared, Exclusive };
// Instead of
enum Mode { kShared, kExclusive };
// Preferred
v8::Global<v8::Promise::Resolver> holder(isolate, resolver);
// When done:
holder.reset();
// Instead of
auto* holder = new v8::Global<v8::Promise::Resolver>(isolate, resolver);
// When done:
delete holder;
Performance optimizations should be validated through benchmarks before implementation. This helps prevent premature optimization and ensures changes actually improve performance in real-world scenarios.
Key practices:
Example:
// Before optimizing this:
const pairs = ArrayPrototypeMap(entries, ({ 0: k, 1: v }) => `${k}:${v}`);
// Run benchmarks to validate if this is better:
let pairs = new Array(entries.length);
for (let i = 0; i < entries.length; i++) {
pairs[i] = `${entries[i][0]}:${entries[i][1]}`;
}
/* Example benchmark results:
n=10: 19,123 ops/sec
n=100: 222,985 ops/sec
n=1000: 1,262,360 ops/sec
n=10000: 3,751,348 ops/sec
n=100000: 4,789,673 ops/sec
*/
This approach helps prevent optimization work that doesn’t provide meaningful benefits while identifying changes that do improve performance significantly.
Base optimization decisions on accurate hardware cost models rather than outdated assumptions. Modern architectures have different performance characteristics for operations than older hardware, especially for SIMD and memory operations.
When modeling operation costs:
For example, instead of using simplified cost models:
// Outdated cost model assumes all floating point operations are expensive
if (varTypeIsFloating(candidate->Expr()->TypeGet()))
{
// floating point loads/store encode larger
cse_def_cost += 2;
cse_use_cost += 1;
}
Consider the actual hardware characteristics:
// Accurate cost model based on instruction encoding and execution cycles
if (varTypeIsFloating(candidate->Expr()->TypeGet()))
{
// 32/64-bit FP move: encoding 4 bytes; execution: 4-7 cycles
cse_def_cost += 4;
cse_use_cost += 2;
}
else if (candidate->Expr()->TypeIs(TYP_SIMD16))
{
// 128-bit SIMD move: encoding 3-4 bytes; execution: 4-7 cycles
cse_def_cost += 3;
cse_use_cost += 2;
}
else if (candidate->Expr()->TypeIs(TYP_SIMD32))
{
// 256-bit SIMD move: encoding 4-5 bytes; execution: 5-8 cycles
cse_def_cost += 4;
cse_use_cost += 3;
}
For alignment decisions, consider both performance and code size goals:
// Balance alignment with code size goals
UNATIVE_OFFSET cnsSize = genTypeSize(targetType);
UNATIVE_OFFSET cnsAlign = (compiler->compCodeOpt() != Compiler::SMALL_CODE) ? cnsSize : 1;
When optimization decisions depend on hardware-specific behavior (like SIMD register spilling), document the rationale to prevent future regressions.
When extending interfaces with new methods or functionality, always implement proper versioning to ensure compatibility across different clients. Breaking changes should increment major versions, while additive changes should increment minor versions. More importantly, implement version checks in your code to gracefully handle compatibility.
For example, when adding a new method to an interface:
// 1. Update the version constant when adding functionality
#define API_INTERFACE_MAJOR_VERSION 2 // Increment for breaking changes
#define API_INTERFACE_MINOR_VERSION 5 // Increment for non-breaking additions
// 2. Implement version checks in code that calls the new functionality
if (runtimeVersion >= API_INTERFACE_MAJOR_VERSION) {
// Call new method safely knowing it exists
interface.NewMethod();
} else {
// Provide alternative implementation or graceful degradation
FallbackImplementation();
}
This approach allows clients on older versions to continue functioning when interacting with newer APIs and vice versa. Remember that versioning is only useful if actively checked during runtime - simply incrementing version numbers without implementing corresponding checks provides no actual compatibility benefits.
Maintain precise and accurate documentation through both JSDoc annotations and explanatory comments. Ensure all technical documentation matches the actual implementation, avoiding inconsistencies between code and documentation.
Key practices:
Example:
/**
* Format help text for printing.
* @param {string} longOption - long option name e.g. 'foo'
* @param {object} optionConfig - option config from parseArgs({ options })
* @returns {string} formatted help text for printing
* @example
* formatHelpTextForPrint('foo', { type: 'string', help: 'help text' })
* // returns '--foo <arg> help text'
*/
function formatHelpTextForPrint(longOption, optionConfig) {
// Implementation using unusual pattern - explain why
if (isSpecialCase(optionConfig)) {
// Document why this special case exists
// and how it affects the output
}
// ... rest of implementation
}
---
## Document code meaningfully
<!-- source: dotnet/runtime | topic: Documentation | language: C# | updated: 2025-06-27 -->
Provide meaningful documentation that enhances code maintainability and understanding. Follow these practices:
1. **Explain the "why"** - Document complex or non-obvious code behavior with comments that explain reasoning, not just functionality:
```csharp
// Checking HasNewValue ensures the property setter is called even with null values,
// as setters may contain important initialization logic that should not be bypassed
if (bindingPoint.HasNewValue || bindingPoint.Value != null)
// TODO: Implement caching mechanism (see issue #1234)
// These definitions are derived from CDDL-licensed source code.
// Original source: https://src.illumos.org/source/xref/illumos-gate/usr/src/uts/common/sys/procfs.h
Document APIs with XML comments - Use XML documentation for public, internal, and significant private APIs to explain their purpose, parameters, and usage patterns, even for parameterless constructors that seed documentation.
Following these practices helps both current and future developers understand code intent, origin, and pending work, significantly reducing maintenance costs.
Enhance search algorithm performance by prioritizing common cases, using appropriate data structures, and avoiding unnecessary operations.
When implementing binary search:
index = ~index; // same as -(index + 1)& (size - 1)) for indexingExample of optimized binary search with fast-path check:
public int findIndex(int[] sortedArray, int value) {
// Fast path: check if value is at the max position
if (sortedArray.length > 0 && sortedArray[sortedArray.length - 1] == value) {
return sortedArray.length - 1;
}
// Standard binary search
int index = Arrays.binarySearch(sortedArray, value);
if (index < 0) {
index = ~index; // same as -(index + 1), insertion point
}
return index;
}
When working with collections, prefer iteration methods that don’t create unnecessary copies. For example, use iterator() instead of methods that create new collections just for iteration.
Maintain consistent naming patterns throughout your code to improve readability and maintainability. Follow these guidelines:
// Incorrect
public static int iCreateProcessInfo() { ... }
// Correct
public static int CreateProcessInfo() { ... }
// Misleading (doesn't return anything)
public static void GetExtendedHelp(ParseResult _) { ... }
// Better
public static void DisplayHelp(ParseResult _) { ... }
// Inconsistent
GetConnectionInfo(connection, out protocol, out cipherSuite, ref negotiatedAlpn, out alpnLength);
// Consistent
GetConnectionInfo(connection, out protocol, out cipherSuite, ref negotiatedAlpn, out negotiatedAlpnLength);
// Poor design (default value is meaningful)
public enum ExtendedLayoutKind
{
None = -1,
CStruct = 0 // This becomes the default!
}
// Better design
public enum ExtendedLayoutKind
{
None = 0, // Default is semantically "none"
CStruct = 1
}
// Unnecessarily verbose when platform uses "Nw" consistently
internal sealed class SafeNetworkFrameworkHandle : SafeHandleZeroOrMinusOneIsInvalid
// Better
internal sealed class SafeNwHandle : SafeHandleZeroOrMinusOneIsInvalid
When selecting names, consider how they’ll be read in context and their alignment with existing patterns in your codebase and the platform.
When implementing performance-critical algorithms, carefully consider memory access patterns. Document alignment assumptions when using low-level operations like MemoryMarshal.Cast or SIMD intrinsics. Prefer reference-based APIs over pinning to avoid GC interference. For bulk operations, use specialized helpers like LoadUnsafe(ref T source, nuint elementOffset) instead of pointer arithmetic.
// Less optimal: Uses pinning that can hinder GC
unsafe void ProcessBytes(ReadOnlySpan<byte> source)
{
fixed (byte* ptr = source)
{
for (int i = 0; i < source.Length - 7; i += 8)
{
ulong value = *(ulong*)(ptr + i);
// Process 8 bytes at once
}
}
}
// More optimal: Uses ref-based API without pinning
void ProcessBytes(ReadOnlySpan<byte> source)
{
ref byte sourceRef = ref MemoryMarshal.GetReference(source);
for (int i = 0; i + 8 <= source.Length; i += 8)
{
// Use LoadUnsafe with explicit offset
ulong value = Vector64.LoadUnsafe(ref sourceRef, (nuint)i);
// Process 8 bytes at once
}
}
When handling data in bulk, consider the impact of alignment requirements and document edge cases like zero-length inputs. For operations on numeric types, select appropriate operations based on platform characteristics (e.g., prefer unsigned division over signed when possible).
Replace panic-prone operations like unwrap() and expect() with graceful error handling that provides meaningful feedback. When encountering unexpected conditions, prefer logging warnings and falling back to reasonable defaults rather than crashing the application.
Key practices:
unwrap() with proper error handling and user-friendly messagesexpect() only when the panic condition truly should never occurExample transformation:
// Avoid this - can panic on unexpected input
let path = output.canonicalize().unwrap().to_string_lossy().to_string();
// Prefer this - graceful error handling
let path = match output.canonicalize() {
Ok(path) => path.to_string_lossy().to_string(),
Err(err) => {
eprintln!("Failed to canonicalize path: {}", err);
return Err(err);
}
};
This approach prevents application crashes from unexpected conditions while still providing useful diagnostic information for debugging and user feedback.
Time-of-Check-to-Time-of-Use (TOCTOU) race conditions occur when a program checks a condition and then uses the result of that check, but the condition might have changed between the check and use. This pattern creates security vulnerabilities, particularly in file system operations, but can affect any multi-step operation where state might change.
To prevent TOCTOU vulnerabilities:
// VULNERABLE TO TOCTOU:
if !Path::new("file.txt").exists() {
File::create("file.txt")?; // Race condition: file might be created here by another process
}
// SAFER APPROACH:
let file = File::options().create_new(true).open("file.txt"); // Atomic operation
// Keep the file open while working with it rather than reopening based on metadata checks
let mut file = File::open("data.txt")?;
// Continue using the same file handle for subsequent operations
Be skeptical of metadata obtained from previous checks, as it may be stale
For security-critical operations, verify that operations succeeded with their intended effect rather than assuming success
Remember that even file locks may be insufficient protection against malicious actors, as they’re often advisory and can potentially be bypassed.
Avoid modifying global security state in favor of connection-specific or context-bound security settings. Global security state changes can lead to unexpected security vulnerabilities when modified by dependencies without application awareness.
When working with security-critical components like TLS:
For example, instead of using global security state changes:
// Avoid globally changing security state
tls.useSystemCA(); // Affects ALL connections
// Preferred: Scope security settings to specific connections
const connection = tls.connect({
ca: tls.getCACertificates('system') // Only affects this connection
});
This approach maintains clear security boundaries and prevents dependencies from silently weakening your application’s security posture through unexpected global state mutations.
Design algorithms that optimize for the most common execution paths by prioritizing frequent scenarios in conditional logic, data structures, and resource allocation.
For conditional code, order checks to minimize branching in frequent cases:
// Better: Common case first requires only one check
if (varTypeUsesIntReg(type))
{
return IntRegisterType;
}
// Less common case
else if (varTypeUsesMaskReg(type))
{
return MaskRegisterType;
}
// Least common case with assertion
else
{
assert(varTypeUsesFloatReg(type));
return FloatRegisterType;
}
Pre-compute constants instead of calculating them at runtime, especially for cross-platform compatibility:
// Better: Pre-computed value
cmp rsi, 0x8000000 // (0x40000000 / sizeof(void*))
// Worse: Compiler-dependent calculation
cmp rsi, (0x40000000 / 8) // sizeof(void*)
Focus testing and optimization efforts on the most frequently used combinations of features rather than exhaustively covering all permutations. This “pay for play” approach ensures users only bear the computational cost for features they actually use.
Files should be named to clearly reflect their primary contents:
Example:
// Good: HTTPThread.zig
const HTTPThread = @This();
// Bad: http_thread.zig
pub const HTTPThread = struct {
// Good: processDependencyList.zig
// Bad: process.zig (too generic)
This convention improves code navigation, maintains consistency, and makes the codebase more intuitive to work with. It also helps prevent confusion when multiple files could potentially have similar generic names.
Always propagate errors with their original context instead of swallowing them or throwing new errors that hide the original cause. This helps with debugging and allows proper error handling up the call stack.
Key practices:
Example of proper error propagation:
// Bad - Swallows error with USE
USE(promise->Then(context, callback));
// Good - Propagates error
if (promise->Then(context, callback).IsEmpty()) {
return; // Error already scheduled
}
// Bad - Hides original error
if (!CreateObject(isolate, context).ToLocal(&obj)) {
return ThrowError("Failed to create object");
}
// Good - Preserves original error
if (!CreateObject(isolate, context).ToLocal(&obj)) {
return; // Original error propagates
}
This practice ensures:
Remove redundant or unnecessary code constructs to improve readability and maintainability. This includes avoiding empty blocks, blocks containing only empty statements, and unnecessary variable declarations that don’t enhance code clarity.
Example (before):
try {
const resource = __addDisposableResource(env, resource_1, true);
{
; // Empty block with just a semicolon
}
}
// Or unnecessary variables:
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
Example (after):
try {
const resource = __addDisposableResource(env, resource_1, true);
// No empty block
}
// Simplified variables:
__assign = Object.assign || function(t) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
t[key] = source[key];
}
}
}
return t;
};
Documentation examples should be self-sufficient, comprehensive, and use proper syntax tags to enable automated validation. Examples must include all necessary context and demonstrate multiple related features rather than isolated snippets. Use v syntax tags instead of generic ones like codeblock to ensure examples are validated by v check-md during CI, keeping them current and consistent.
For example, instead of a minimal snippet:
ch.try_push(42)
Provide a complete, informative example:
ch := chan int{cap: 2}
println(ch.try_push(42)) // `.success` if pushed, `.not_ready` if full, `.closed` if closed
println(ch.len) // Number of items in the buffer
println(ch.cap) // Buffer capacity
println(ch.closed) // Whether the channel is closed
This approach makes examples immediately usable for learning and ensures they remain valid through automated testing, while providing comprehensive coverage of related functionality.
Always use proper memory alignment for SIMD (Single Instruction, Multiple Data) operations to maximize performance. When implementing vector operations:
Example:
// Prefer this approach with platform-specific optimizations
#if HAVE_ALIGNED_ALLOC
return aligned_alloc(alignment, size);
#elif HAVE_POSIX_MEMALIGN
void* result = nullptr;
posix_memalign(&result, alignment, size);
return result;
#else
#error "Platform doesn't support aligned_alloc or posix_memalign"
#endif
// For memory operations, leverage alignment hints when available
#ifdef TARGET_ARCHITECTURE_WITH_ALIGNMENT_HINTS
// Define aligned versions that use hardware alignment hints
case OP_LOADX_ALIGNED_MEMBASE:
case OP_STOREX_ALIGNED_MEMBASE_REG:
#endif
Proper alignment can lead to dramatic performance improvements for vector operations (up to 295x faster for some SIMD comparison operations) by enabling hardware-specific optimizations and avoiding cache-line splitting.
Write code that prioritizes readability and maintainability over cleverness or excessive optimization. Avoid overly clever techniques that might confuse other developers or make the code harder to maintain.
Key principles:
Example - Instead of this clever approach:
const handleTypes = ['TCP', 'TTY', 'UDP', 'FILE', 'PIPE', 'UNKNOWN'];
setOwnProperty(handleTypes, -1, null);
// Later using handleTypes[someIndex]
Prefer this more explicit approach:
const handleTypes = ['TCP', 'TTY', 'UDP', 'FILE', 'PIPE', 'UNKNOWN'];
function getHandleType(type) {
return type >= 0 && type < handleTypes.length ? handleTypes[type] : null;
}
Similarly, prefer direct string creation over array joining for static content:
// Instead of:
const message = [
'Error: This file cannot be parsed as either CommonJS or ES Module.',
'- CommonJS error: await is only valid in async functions.',
'- ES Module error: require is not defined in ES module scope.'
].join('\n');
// Prefer:
const message = 'Error: This file cannot be parsed as either CommonJS or ES Module. ' +
'CommonJS error: await is only valid in async functions. ' +
'ES Module error: require is not defined in ES module scope.';
When functions are called multiple times, consider the performance implications of inlining vs. extracting. Unnecessary function extraction can hinder compiler optimizations in hot paths.
Configuration settings should be self-documenting with clear intent. When adding temporary workarounds, conditional flags, or platform-specific settings, include descriptive comments explaining their purpose, limitations, and lifecycle. Use TODO comments with issue references for temporary configurations, and choose clear, descriptive names for all configuration properties. This practice helps future developers understand why certain configurations exist and when they can be modified or removed.
Examples:
2. For feature flags with specific scope:
```xml
<FeatureJavaMarshal Condition="'$(Configuration)' == 'Debug' or '$(Configuration)' == 'Checked' or '$(TargetOS)' == 'Android'">true</FeatureJavaMarshal>
---
## Reuse computed values efficiently
<!-- source: nodejs/node | topic: Performance Optimization | language: Other | updated: 2025-06-24 -->
Move variable declarations and computations outside of loops when their values don't change between iterations. This reduces memory allocations and avoids redundant computations, improving performance.
Example - Before:
```cpp
for (const auto& resource_entry : manager->held_locks_) {
for (const auto& held_lock : resource_entry.second) {
Local<Object> lock_info = CreateLockInfoObject(...);
// Use lock_info
}
}
Example - After:
Local<Object> lock_info;
for (const auto& resource_entry : manager->held_locks_) {
for (const auto& held_lock : resource_entry.second) {
lock_info = CreateLockInfoObject(...);
// Use lock_info
}
}
Similarly, cache computed values that are used repeatedly:
// Before: Computing in every call
std::string normalised_cache_dir = NormalisePath(cache_dir);
// After: Store as member variable
class CompileCacheHandler {
std::string normalised_cache_dir_;
// Compute once in constructor
};
When implementing networking functionality, ensure code uses platform-agnostic APIs and appropriate abstraction layers to handle differences between Windows, Unix, and macOS systems. Similar to how string operations require platform-specific handling (as seen in discussion 1 where strchr was replaced with app_candidate.find(DIR_SEPARATOR)), networking code should avoid direct OS-specific socket APIs in favor of cross-platform abstractions.
Example:
// Avoid platform-specific code like this:
#ifdef _MSC_VER
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
// Windows-specific socket operations
#else
int sock = socket(AF_INET, SOCK_STREAM, 0);
// Unix-specific socket operations
#endif
// Instead, use platform abstraction layers:
pal::socket_t sock = pal::create_socket(pal::AddressFamily::IPv4, pal::SocketType::Stream);
if (pal::connect_socket(sock, address, port))
{
// Platform-agnostic socket operations
}
This approach ensures consistent behavior across all supported platforms and simplifies maintenance by centralizing platform-specific code in the abstraction layer.
Name variables, properties, and functions to accurately reflect their purpose and actual usage in code, not just their technical classification. This ensures other developers can understand the intent without having to analyze implementation details.
Key guidelines:
Example:
// Avoid misleading names
var _maskData = new int[size]; // Not actually used as a mask
// Better - name reflects actual usage
var _selectParameters = new int[size]; // Used as parameters in select operations
// For configurations, prefer descriptive names over technical terms
// Less clear for users:
options.WasmEnableEventPipe = true;
// More descriptive of purpose:
options.WasmEnablePerformanceTracing = true;
Follow this principle even when a name seems technically correct - what matters is whether it effectively communicates how the element is actually used in your codebase.
Maintain efficient and properly structured CI/CD workflows by removing unnecessary dependencies and using consistent formatting patterns. Each workflow step should serve a specific purpose for the job it’s part of, and matrix configurations should follow a consistent structure.
Remove unnecessary setup steps:
# AVOID: Including unused toolchains or setup steps
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
- uses: dtolnay/rust-toolchain@fcf085fcb4b4b8f63f96906cd713eb52181b5ea4 # stable
- uses: ./.github/actions/setup-go
# PREFER: Only include steps required for the specific job
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
Use consistent matrix configuration structures:
# PREFER: Use structured objects for better readability and extensibility
runtime:
- { name: node, version: 20 }
- { name: node, version: 18 }
- { name: bun, version: 'latest' }
# For includes, maintain the same structure:
include:
- { os: ubuntu-latest, runtime: { name: node, version: 20 }, bundle: false }
Regular workflow audits should be performed to remove outdated or unnecessary steps that might slow down builds or waste resources. Well-structured workflows are easier to maintain, extend, and debug when issues arise.
Choose names that clearly convey their purpose and avoid ambiguous identifiers that can be misinterpreted. This applies to methods, variables, parameters, and types.
Key principles:
Examples:
Instead of confusing boolean parameters:
// Bad - unclear what true/false means
fn parse_inner(s: &str, allow_subdomain_wildcard: bool)
// Good - descriptive enum makes intent clear
enum SubdomainWildcardSupport {
Enabled,
#[default]
Disabled
}
fn parse_inner(s: &str, wildcard_support: SubdomainWildcardSupport)
Instead of ambiguous method names:
// Bad - unclear what this cancels
fn op_read_cancel()
// Good - clearly describes the operation
fn op_read_with_cancel_handle()
// Bad - too generic when context matters
fn get_usage()
// Good - specific to the type of usage
fn get_cpu_usage()
Instead of unclear type names:
// Bad - not descriptive enough
struct Unconfigured
// Good - clearly describes the state
struct UnconfiguredRuntime
This practice is especially important in permission-related code and public APIs where clarity prevents misuse and security issues.
Always validate values before performing unsafe operations like dynamic casting. When a value could be null, zero, or undefined, store it in an intermediate variable and verify it before proceeding.
Bad:
// Risky: jsDynamicCast on a zero value is undefined behavior
auto* existing_tag = jsDynamicCast<Bun::NapiTypeTag*>(globalObject->napiTypeTags()->get(js_object));
// Dangerous: No verification before dereferencing
return handleSlot ? JSC::JSValue::encode(*handleSlot) : JSC::JSValue::encode(JSC::JSValue());
Good:
// Safe: Store the result first, then check before casting
JSValue napiTypeTagValue = globalObject->napiTypeTags()->get(js_object);
auto* existing_tag = jsDynamicCast<Bun::NapiTypeTag*>(napiTypeTagValue);
// Better: Assert your assumptions explicitly
ASSERT(handleSlot);
return JSC::JSValue::encode(*handleSlot);
For functions that can return null or trigger exceptions, add appropriate assertions after critical operations:
// Assert getDirect doesn't return null
auto value = thisObject->getDirect(vm, builtinNames.statePrivateName());
ASSERT(value);
// Check for exceptions after conversions
auto state = value.toInt32(lexicalGlobalObject);
assertNoExceptionExceptTermination();
This approach prevents undefined behavior, makes code intent clearer, and helps catch bugs earlier in the development process.
Add clear comments to explain complex logic, function differences, and non-obvious implementation details. This is especially important for:
When a future developer reads your code, they should understand the reasoning without needing to reverse-engineer the logic. Documentation is particularly valuable for code that will be maintained by others.
For example:
// Document differences between similar functions
// hashDigest: Creates a fixed-length hash from input data
// xofHashDigest: Creates an extendable-output function hash with custom length
DataPointer hashDigest(const Buffer<const unsigned char>& data,
const EVP_MD* md);
DataPointer xofHashDigest(const Buffer<const unsigned char>& data,
const EVP_MD* md,
size_t length);
// Explain non-intuitive checks
if (content.front() == '[') {
// This identifies the start of a section like [section_name]
// ...
}
// Document complex methods
// InitWldp: Initializes Windows Lockdown Policy features
// Loads required DLLs and resolves function pointers for code integrity checks
void InitWldp(Environment* env) {
// implementation...
}
Prioritize documentation that answers “why” over “what” when the code itself already clearly shows what it’s doing.
Always explicitly await all asynchronous operations, especially in cleanup code paths and resource management. Failing to properly await promises can lead to race conditions, resource leaks, and unpredictable behavior in concurrent environments.
When writing concurrent code:
// Good: Ensures the resource is fully closed before continuing if (options.autoClose) await this.close();
2. Use modern Promise patterns for cleaner async code:
```javascript
// Better: Use Promise.withResolvers() for cleaner promise creation
const { promise, resolve, reject } = Promise.withResolvers();
fs.stat(filePath, { signal }, (err, stats) => {
if (err) {
return reject(err);
}
resolve(stats);
});
await assert.rejects(promise, { name: 'AbortError' });
// Ensure AsyncLocalStorage context is preserved in callbacks
const als = new AsyncLocalStorage();
als.run(123, () => {
navigator.locks.request('lock-name', async (lock) => {
// Context should be maintained across the async boundary
assert.strictEqual(als.getStore(), 123);
});
});
By properly awaiting promises and managing async context, you’ll write more reliable concurrent code with fewer race conditions and resource leaks.
Tests should focus on verifying behavior rather than implementation details to ensure they remain robust during refactoring. Avoid dependencies on timers or internal code structure that can lead to flaky and brittle tests.
Always use appropriate assertion utilities to verify expected behavior:
deepStrictEqual for comprehensive object comparison rather than checking individual propertiescommon.mustCall()// ❌ Fragile: Tests implementation details
assert(tapReporterSource.includes('return result; // Early return...'));
// ✅ Robust: Tests actual behavior with proper utilities
const result = await reader.read(new Uint8Array(16));
assert.deepStrictEqual(result, {
value: expectedBytes,
done: false
});
// ✅ Verify callbacks execute
reader.read(receive).then(common.mustCall((result) => {
assert.strictEqual(result.done, false);
}));
Avoid using timers in tests as they create non-deterministic behavior, leading to flakiness and slowness. Instead, use event hooks or synchronous assertions that verify the expected outcome directly.
Always check for exceptions immediately after operations that might throw them, especially before using the results in control flow decisions. When accessing properties that might be used in conditionals, retrieve the property first, check for exceptions, and then use the property in the conditional.
Incorrect pattern:
if (JSValue property = object->getIfPropertyExists(globalObject, propertyName)) {
// Use property...
RETURN_IF_EXCEPTION(scope, {}); // Too late - exception may have occurred during getIfPropertyExists
}
Correct pattern:
JSValue property = object->getIfPropertyExists(globalObject, propertyName);
RETURN_IF_EXCEPTION(scope, {}); // Check immediately after the operation
if (property) {
// Now safe to use property...
}
This pattern applies to any function that might throw exceptions, including property access, method calls, and object construction. Ensure exception checking occurs after each potentially throwing operation and before using its results. Use consistent macros like RETURN_IF_EXCEPTION or RELEASE_AND_RETURN based on your codebase’s conventions.
For property chains or multiple operations, check after each step:
auto value = params->get(globalObject, property);
RETURN_IF_EXCEPTION(scope, nullptr);
// Now safe to use value
Following this pattern prevents hard-to-debug crashes and ensures exceptions are properly handled throughout all code paths.
Documentation should follow consistent formatting and structural patterns to maximize readability and maintainability. Key guidelines:
Example of proper documentation formatting:
# Feature Name
Default behavior description first, followed by any exceptions or special cases.
## Usage
```js
// Code example with proper indentation
const example = require('node:example');
example.doSomething();
option1 {string} Description fits within 80 chars, continues on next
line with proper indentation.option2 {Object} Another option description.
This format ensures consistency across documentation and makes it easier for users to find and understand information.
---
## Structure configuration options
<!-- source: dotnet/runtime | topic: Configurations | language: C++ | updated: 2025-06-20 -->
Design and document configuration options (feature flags, environment variables, conditional compilation) to be clear, consistent, and maintainable. Group related configurations together and avoid unnecessary splits or redundancies. Document the purpose and behavior of each option, including valid values and interaction effects.
For conditional compilation:
```cpp
// GOOD: Clear hierarchy and purpose
#if !defined(DACCESS_COMPILE) && !defined(FEATURE_CDAC_UNWINDER)
// Code for in-process execution only
#endif
// AVOID: Redundant or overly specific conditions
#if defined(HOST_WINDOWS) // Redundant if file is Windows-only
// ...
#endif
For environment variables, provide clear levels with documented behavior:
// GOOD: Well-documented configuration with clear levels
// DOTNET_InterpreterMode=0: default, interpreter only used with explicit opt-in
// DOTNET_InterpreterMode=1: use interpreter except for R2R code and System.Private.CoreLib
// DOTNET_InterpreterMode=2: use interpreter for everything except intrinsics
// DOTNET_InterpreterMode=3: full interpreter-only mode, no fallbacks
For feature flags, consolidate related functionality until there’s a clear need for separation:
// PREFER: Single feature flag for closely related functionality
#ifdef FEATURE_JAVAMARSHAL
// All Java interop code including GC bridge
#endif
// AVOID: Premature separation that creates confusing boundaries
#ifdef FEATURE_JAVAMARSHAL
// Some Java interop code
#endif
#ifdef FEATURE_GCBRIDGE
// Closely related GC bridge functionality
#endif
Keep configuration dependencies minimal and platform-aware. Avoid including unnecessary configuration files or external tools that can introduce unpredictable behaviors or unrelated settings. For platform-specific code, use conditional compilation to ensure that components are only built and executed in relevant environments.
When working with build configurations:
Example of good practice for platform-specific code:
// For header files
#ifndef SRC_NODE_PLATFORM_FEATURE_H_
#define SRC_NODE_PLATFORM_FEATURE_H_
#ifdef _WIN32
// Windows-specific declarations
#endif
// Cross-platform declarations
#endif // SRC_NODE_PLATFORM_FEATURE_H_
// For implementation files
#ifdef _WIN32
// Windows-specific implementation
#endif
Example of good configuration file practice:
// Only include what you need
'includes': ['toolchain.gypi'], // Correct if only toolchain config is needed
// Avoid unnecessary inclusions
'includes': ['toolchain.gypi', 'features.gypi'], // Incorrect if features.gypi adds unneeded complexity
Documentation should be complete, properly formatted, and clearly explain purpose with adequate examples. When writing documentation, ensure that:
For example, when documenting an attribute with requirements:
// Good: Comprehensive examples showing both valid and invalid usage
[[clang::sycl_external]] void Foo(); // Ok.
[[clang::sycl_external]] void Bar() { /* ... */ } // Ok.
[[clang::sycl_external]] extern void Baz(); // Ok.
[[clang::sycl_external]] static void Quux() { /* ... */ } // error: Quux() has internal linkage.
Similarly, when adding functions with potentially ambiguous names, include comments explaining their purpose:
// Generates a concise string representation of a NamedDecl for diagnostic purposes
SmallString<128> toTerseString(const NamedDecl &D) const;
This approach prevents confusion and ensures documentation serves as an effective reference for both current and future developers.
When modifying configuration systems, prioritize backward compatibility unless there’s an explicit breaking change planned. If a breaking change is necessary, implement compatibility handlers for older configurations and clearly communicate the transition plan.
For example, when modifying configuration enums or data structures:
// When adding new configuration values, append them rather than reordering
// to maintain compatibility with serialized configurations
enum ConfigValues
{
// Existing values - don't change these ids
Value1 = 1,
Value2 = 2,
// New values - add at the end
NewValue = 3
}
// For major changes, implement version detection and compatibility handling
public ProcessConfig(ConfigData data)
{
if (data.Version < CurrentVersion) {
// Handle older format compatibility
return ProcessLegacyConfig(data);
}
// Process current format
}
This approach ensures tools and systems can continue to process configurations from previous versions, maintaining reliability across version updates. As seen in the discussions, breaking configuration compatibility (like removing enum definitions) can break tools that need to process older versions, requiring changes to be reverted or additional compatibility code to be written.
When designing or implementing APIs, always prefer publicly documented APIs over internal ones. Internal APIs may change without notice as they aren’t subject to semantic versioning guarantees.
If you need functionality that’s only available through internal APIs, either:
For example, instead of using internal modules:
// Avoid this
const { setSourceMapsSupport } = require('internal/source_map/source_map_cache');
const { SourceMap } = require('internal/source_map/source_map');
// Prefer this
const { setSourceMapsSupport } = require('node:module');
const { SourceMap } = require('node:module');
When exposing new APIs, ensure they’re properly documented and don’t leak internal implementation details. If an API returns values with specific formats (like ‘commonjs-typescript’), either document these formats as part of the public contract or convert them to standard documented values.
Using internal APIs can lead to code that breaks unexpectedly during updates, creates maintenance burdens, and prevents APIs from being properly stabilized in the future.
Ensure dependency configurations are explicit and predictable to avoid confusion and unexpected build behavior. Avoid package aliases that obscure the actual dependency being used, prefer published versions over git dependencies for stability, and be explicit about features that control critical behaviors like linking.
When specifying dependencies:
ring = { version = "1.0.0", package = "aws-lc-rs" } can cause long-term confusionesbuild_rs = { version = "0.1.0", git = "..." } should use a released versionlcms2 = { version = "6.1.0" } may silently fall back to static linking; disable static-fallback feature if dynamic linking is requiredThis prevents silent fallbacks, reduces confusion about what’s actually being used, and makes build behavior more predictable across different environments.
When implementing or documenting algorithms, clearly specify their behavior, expected outputs, and usage patterns. This includes documenting mathematical operation semantics, output characteristics, and providing complete examples that demonstrate the algorithm’s behavior.
Key aspects to document:
Example from cryptographic algorithm documentation:
// Alice derives shared secret with her own private key with Bob's public key
alice_shared_sec := curve25519.derive_shared_secret(mut alice_pvkey, bob_pbkey)!
// Bob derives shared secret with his own private key with Alice's public key
bob_shared_sec := curve25519.derive_shared_secret(mut bob_pvkey, alice_pbkey)!
// the two shared secrets (derived by Alice, and derived by Bob), should be the same
// Output: 32-byte shared secret
println('Alice secret: ${alice_shared_sec}')
println('Bob secret: ${bob_shared_sec}')
This ensures developers understand not just how to use the algorithm, but what to expect from it and how it behaves in different scenarios.
Always use fourslash tests (for IDE/language service features) or compiler tests (for TypeScript compilation behavior) instead of direct unit tests. Place tests in the correct directories: tests/cases/compiler or tests/cases/fourslash.
Fourslash tests validate IDE features like completions and refactoring:
/// <reference path='fourslash.ts'/>
//// interface User {
//// name: string;
//// }
////
//// const user: User = {
//// /*completion*/
//// };
verify.completions({
marker: "completion",
includes: { name: "name" }
});
Compiler tests validate TypeScript type checking:
// @Filename: test.ts
// @strict: true
interface Config {
required: string;
optional?: number;
}
const config1: Config = { required: "test" }; // Should work
const config2: Config = { optional: 42 }; // Should error - missing required
When adding tests, ensure you run them individually first with hereby runtests --tests=path/to/test.ts to verify they demonstrate the expected behavior correctly before committing.
When designing APIs, provide error messages that are contextually accurate, descriptive, and actionable. Generic error suggestions can confuse users when they don’t apply to the specific situation. Instead, tailor error responses to include:
For example, instead of a generic message like:
Could not find a declaration file for module 'bar'. Try `npm i --save-dev @types/bar` if it exists or add a new declaration (.d.ts) file containing `declare module 'bar';`
A more contextually aware message might be:
Could not find a declaration file for module 'bar' at '/path/to/bar.js'. This file is being treated as a JavaScript module. Consider one of the following:
- If this is your own module, add type definitions with a matching .d.ts file
- If using a third-party module, check if @types/bar exists and is compatible with your version
- If neither applies, verify the module path is correct
Error messages that adapt to the specific situation help API consumers resolve issues more efficiently and reduce support burden.
Configure consistent module resolution strategies across related projects and ensure package.json files are properly set up for the chosen strategy. Different strategies (Node10, NodeNext) have distinct behaviors regarding package.json handling, which can cause subtle resolution errors and confusing tracing output.
For example, when using NodeNext:
// tsconfig.json
{
"compilerOptions": {
"moduleResolution": "NodeNext",
// Ensure traceResolution is consistently set across projects if needed for debugging
"traceResolution": true
}
}
And ensure your package.json correctly defines module entry points:
// package.json
{
"name": "my-package",
"main": "build/index.js", // Used by Node.js and some module resolution strategies
"types": "build/index.d.ts" // Used by TypeScript to find type declarations
}
When troubleshooting module resolution issues, remember that inconsistent configuration can lead to unexpected behavior. If you need to trace module resolution, make sure the traceResolution option is set consistently across all relevant projects in your workspace.
When implementing attributes, language features, or compiler diagnostics, always provide explicit error diagnostics rather than default warnings for misuse scenarios. Error messages should be clear, specific about the violation, and consistent with existing diagnostic patterns in the codebase.
For attributes, explicitly request error diagnostics using ErrorDiag in the SubjectList to ensure consistent behavior across all attributes. For custom error messages, follow established naming conventions and provide specific guidance about correct usage.
Example of proper error diagnostic implementation:
// In Attr.td - Request explicit error diagnostics
def SYCLExternal : InheritableAttr {
let Spellings = [Clang<"sycl_external">];
let Subjects = SubjectList<[Function], ErrorDiag>; // Use ErrorDiag, not default warning
}
// In DiagnosticSemaKinds.td - Provide clear, specific error messages
def err_sycl_external_invalid_linkage : Error<
"'sycl_external' can only be applied to functions with external linkage">;
This approach ensures users receive immediate, clear feedback about incorrect usage rather than potentially overlooked warnings, leading to better code quality and faster debugging cycles.
Maintain consistent formatting and use concise, standard syntax throughout the codebase. This includes several specific formatting rules:
# symbol-n instead of ! -z)Examples:
// Good - no space after #
#pragma warning(push)
#pragma GCC diagnostic push
// Bad - space after #
# pragma warning(push)
# pragma GCC diagnostic push
// Good - concise string concatenation
auto path = binary_directory + "/" + filename;
// Bad - verbose string construction
auto path = binary_directory + std::string("/") + std::string(filename);
# Good - concise test operator
if [[ -n "$variable" ]]; then
# Bad - verbose test combination
if [[ ! -z "$variable" ]]; then
These formatting preferences improve code readability and maintain consistency across different languages and contexts in the codebase.
Documentation should be both technically accurate and contextually helpful for developers. Comments and JSDoc entries must correctly describe the behavior, purpose, and relationships of code elements.
Key practices:
Bun.BodyInit instead of just BodyInit)Examples:
Instead of vague comments:
// keep this in sync with src/bun.js/bindings/webcore/JSReadableStream.cpp customInspect
// keep this in sync with src/bun.js/bindings/webcore/JSWritableStream.cpp customInspect
Use explanatory comments that convey purpose:
// These must match the order of the stateNames arrays in JSReadableStream.cpp and JSWritableStream.cpp
Instead of imprecise JSDoc:
/**
* Local IP address connected to the socket
* @example "192.168.1.100" | "2001:db8::1"
*/
readonly localFamily: string;
Use technically accurate descriptions:
/**
* IP protocol family used for the local endpoint of the socket
* @example "IPv4" | "IPv6"
*/
readonly localFamily: string;
Use defensive programming patterns to prevent null-related issues before they occur. This includes creating safe object copies to prevent mutations, using explicit null/undefined checks, and initializing objects safely.
Key practices:
{ ...originalObject } instead of direct assignmentmessage.length === 0 instead of message === ""{ __proto__: null } to prevent prototype pollution?? true or ?? false is the correct defaultExample:
// Good - defensive copy prevents mutation
ObjectAssign(internals, { core, nodeGlobals: { ...nodeGlobals } });
// Good - explicit length check
const formattedMessage = message.length === 0 ? "" : `${message} `;
// Good - null prototype prevents pollution
attributes = { __proto__: null };
This approach prevents null reference errors, unintended mutations, and makes null handling intentions explicit in the code.
When accessing the same property or calling the same function multiple times, especially in loops or performance-critical sections, cache the result in a local variable. This reduces redundant memory access operations and improves execution speed.
Why it matters: Each property access or function call involves memory lookups and potentially complex operations. Repeating these unnecessarily can significantly impact performance in hot code paths.
Example - Before optimization:
// Repeated access to nested property in each loop iteration
for (int i = 0; i < sk_X509_num(default_ca_certificates->root_extra_cert_instances); i++) {
X509 *cert = sk_X509_value(default_ca_certificates->root_extra_cert_instances, i);
// Use cert...
}
Example - After optimization:
// Cache the property access before the loop
auto instances = default_ca_certificates->root_extra_cert_instances;
for (int i = 0; i < sk_X509_num(instances); i++) {
X509 *cert = sk_X509_value(instances, i);
// Use cert...
}
This technique is particularly important for:
Use the appropriate invocation syntax for functions based on their intended usage. Regular functions should be called directly without the new keyword, while constructor functions (typically capitalized) should be instantiated with new.
Incorrect:
// Using constructor syntax with a regular function
const context2 = new vm.createContext(context);
Correct:
// Calling a regular function properly
const context2 = vm.createContext(context);
// Using constructor syntax with an actual constructor
const myDate = new Date();
This distinction is important for code correctness and readability. Using new with a non-constructor function can lead to unexpected behavior, while omitting new when required can cause the function to execute in the global context instead of creating a new object instance.
When implementing performance-critical algorithms, design your code to detect and utilize platform-specific hardware features while maintaining compatibility across different architectures. Create abstraction layers that allow the same algorithm to run on various platforms but take advantage of specialized instruction sets (like AVX extensions) when available.
For example, instead of directly using platform-specific code:
// Not recommended - tightly coupled to specific hardware
public void ProcessData(float[] data)
{
if (Avx2.IsSupported)
{
// Use AVX2 instructions
}
else
{
// Fallback implementation
}
}
Create a strategy pattern that selects the appropriate implementation:
// Recommended approach
public interface IDataProcessor
{
void ProcessData(float[] data);
}
public class DataProcessorFactory
{
public static IDataProcessor Create()
{
if (Avx2.IsSupported)
{
return new Avx2DataProcessor();
}
else if (Sse.IsSupported)
{
return new SseDataProcessor();
}
return new DefaultDataProcessor();
}
}
This approach facilitates platform-specific optimizations while maintaining clean separation of concerns, makes testing easier, and allows for future extensions as new instruction sets become available.
Always document function contracts completely, even when surrounding code lacks documentation. Include header comments that describe the function’s purpose and behavior, and explicitly specify preconditions using appropriate annotations (like PRECONDITION) to clarify execution requirements.
For example:
// Processes cross-component references during garbage collection
// Returns true if any references were processed
void Interop::TriggerClientBridgeProcessing(
_In_ size_t sccsLen,
_In_ StronglyConnectedComponent* sccs,
_In_ size_t ccrsLen,
_In_ ComponentCrossReference* ccrs)
{
CONTRACTL
{
NOTHROW;
GC_NOTRIGGER;
PRECONDITION(g_GCBridgeActive && "Must be called during GC suspension");
}
CONTRACTL_END;
// Implementation...
}
Clear function documentation improves code readability, facilitates maintenance, and helps other developers understand code execution requirements without having to trace through all callers. Even when existing code lacks proper documentation, always document new functions completely to establish better practices over time.
Identify and optimize frequently executed code paths to improve performance. Hot paths have a significant impact on overall application performance.
Key strategies include:
#[inline] to small functions called in performance-critical sections:
#[doc(hidden)]
#[inline] // Added to improve inlining in hot paths
pub fn has_budget_remaining() -> bool {
// Function body
}
pub fn poll_next_many(/* params */) -> Poll<usize> {
if limit == 0 || self.entries.is_empty() {
return Poll::Ready(0); // Early return avoids unnecessary work
}
// Remaining logic
}
// Instead of obtaining a lock to read a constant value
fn get_shard_size(&self) -> u32 {
// Store this as a field instead of reading through a lock
self.shard_count // Direct field access instead of lock.read().len()
}
Minimize atomic operations: Evaluate whether atomic operations in hot paths can be avoided or optimized.
// Instead of using map with generic logic
impl::spawn_child_with(&mut self.std, with) // Direct function call
Each optimization may seem small individually, but they can significantly improve performance when applied to code that executes thousands or millions of times.
When adding functionality or fixing bugs, always add new test functions rather than modifying existing ones to preserve test coverage. Ensure comprehensive testing by including edge cases, error conditions, and assertions for all public functions. Use meaningful, non-default values in tests to improve sensitivity to regressions.
Key practices:
Example:
// Good: Adding new test function with comprehensive coverage
fn test_new_feature_success_case() {
result := my_function(42) // Use meaningful non-zero value
assert result == expected_value
}
fn test_new_feature_error_case() {
if _ := my_function(-1) {
assert false
} else {
assert true
}
}
// Avoid: Modifying existing test functions or using only default values
This approach maintains existing test coverage while ensuring new functionality is thoroughly validated, making code reviews easier and preventing regressions.
Complex logical expressions should be extracted into well-named variables or functions to improve code readability and maintainability. This applies to:
Example - Before:
const classOrInterfaceDeclaration = declaration.parent?.kind === SyntaxKind.Constructor && getSyntacticModifierFlags(declaration) & ModifierFlags.AccessibilityModifier ? declaration.parent.parent : declaration.parent;
Example - After:
const isConstructorParamWithAccessModifier = declaration.parent?.kind === SyntaxKind.Constructor &&
getSyntacticModifierFlags(declaration) & ModifierFlags.AccessibilityModifier;
const classOrInterfaceDeclaration = isConstructorParamWithAccessModifier ? declaration.parent.parent : declaration.parent;
The extracted variable name should clearly describe the condition’s purpose. For frequently used conditions, consider extracting them into helper functions that can be reused across the codebase.
Select data structures and algorithms based on performance characteristics and actual usage patterns rather than convenience or familiarity. Consider memory efficiency, computational complexity, and scaling behavior when making implementation choices.
Key principles:
map[T]bool instead of map[T]T for sets, since sizeof(bool) < sizeof(T) for most types)Example of efficient set implementation:
// Inefficient - stores redundant data
struct BadSet[T] {
mut:
main_set map[T]T
}
// Efficient - minimal memory usage
struct GoodSet[T] {
mut:
main_set map[T]bool
}
fn (g GoodSet[T]) elements() []T {
return g.main_set.keys() // Use builtin method instead of manual iteration
}
Example of algorithm selection for duplicate removal:
// Inefficient for large datasets - O(n²) complexity
fn remove_duplicates_slow[T](arr []T) []T {
mut results := []T{}
for val in arr {
if val !in results { // Linear search each time
results << val
}
}
return results
}
// Efficient - O(n) complexity
fn remove_duplicates_fast[T](arr []T) []T {
mut seen := map[T]bool{}
mut results := []T{}
for val in arr {
if !seen[val] {
seen[val] = true
results << val
}
}
return results
}
Always ensure locks are acquired and released in a deterministic manner in concurrent code. Use defer statements immediately after lock acquisition to guarantee release along all code paths, and acquire locks before accessing the protected resources to prevent race conditions.
Example of proper lock usage:
pub fn someOperation(self: *SomeStruct) void {
self.mutex.lock();
defer self.mutex.unlock();
// If reference counting is involved, also defer the deref
defer self.deref();
// Access protected resources safely here...
// All code paths will automatically release the lock
}
Incorrect pattern that can lead to race conditions:
// AVOID: Accessing protected data before acquiring the lock
var item = self.shared_map.get(key); // Race condition!
self.lock.lock();
// ... operations with item ...
self.lock.unlock();
Correct approach:
// CORRECT: Acquire lock first, then access protected data
self.lock.lock();
defer self.lock.unlock();
var item = self.shared_map.get(key); // Safe access
// ... operations with item ...
This pattern eliminates duplicate unlock calls along different code paths, prevents deadlocks, and ensures thread safety when accessing shared resources.
When working with database operations, prioritize efficiency by avoiding redundant query executions. Instead of executing statements multiple times to gather different pieces of information (like metadata or column types), cache or collect all necessary data during the initial execution.
For example, when working with SQLite prepared statements and needing column types:
// Instead of this:
sqlite3_reset(stmt);
sqlite3_step(stmt);
// Now get column types...
// Do this:
// During initial execution:
if (sqlite3_step(stmt) == SQLITE_ROW) {
// Store column types while processing the result
for (int i = 0; i < columnCount; i++) {
columnTypes[i] = sqlite3_column_type(stmt, i);
}
// Continue with normal row processing...
}
This approach is especially important for potentially expensive queries, as redundant executions can significantly impact performance. Additionally, when working with database APIs, be mindful of version-specific constants and methods (like SQLITE_TEXT vs. SQLITE3_TEXT), particularly in codebases that might interact with multiple versions of the same database system.
When calling functions that process buffers, especially external C functions, always provide explicit buffer size parameters to prevent buffer overflow vulnerabilities. Buffer overflows are a common security vulnerability that can lead to memory corruption, unauthorized data access, and even remote code execution.
Example (incorrect):
const res = Bun__writeHTTPDate(buffer, timestampMs);
Example (secure):
const res = Bun__writeHTTPDate(buffer, buffer.len, timestampMs);
This is particularly critical for foreign function interface (FFI) calls where the called function may not implement its own buffer boundary checks. Always verify the expected parameters for external functions and ensure buffer lengths are explicitly passed to maintain security boundaries.
Identify and eliminate unnecessary memory allocations in performance-critical code paths. This includes pre-allocating collections when the size is known, using static strings for common values, and avoiding intermediate data structures.
Key strategies:
Cow::Borrowed or FastStaticString instead of allocating new stringsExample from HTTP method handling:
// Instead of always allocating:
fn method_slow(method: &http::Method) -> String {
method.to_string() // Always allocates
}
// Use static strings for common cases:
fn method_fast(method: &http::Method) -> Cow<'static, str> {
match *method {
Method::GET => Cow::Borrowed("GET"),
Method::POST => Cow::Borrowed("POST"),
// ... other common methods
_ => Cow::Owned(method.to_string()),
}
}
This optimization is particularly important in hot code paths where even small allocations can compound into significant performance overhead. Always measure the impact, as the complexity trade-off should be justified by measurable performance gains.
Wrap all code identifiers, method names, interface names, class names, and API references in JSDoc comments with backticks (`) for proper monospacing and improved readability.
This includes:
EventTarget, EventListener)addEventListener(), handleEvent())Example:
/**
* The `EventListenerObject` interface represents an object that can handle events
* dispatched by an `EventTarget` object.
*
* This interface provides an alternative to using a function as an event listener.
* When implementing an object with this interface, the `handleEvent()` method
* will be called when the event is triggered.
*/
interface EventListenerObject {
handleEvent(evt: Event): void;
}
Instead of:
/**
* The EventListenerObject interface represents an object that can handle events
* dispatched by an EventTarget object.
*
* When implementing an object with this interface, the handleEvent() method
* will be called when the event is triggered.
*/
This formatting convention ensures consistent documentation styling, improves code readability in generated documentation, and follows standard JSDoc best practices for technical documentation.
Apply C++ move semantics and lambda modifiers only when necessary. The mutable keyword should only be used in lambdas when you need to modify captured variables, including when using move operations on them:
// Correct: mutable required for WTFMove(message)
context->postImmediateCppTask([protectedThis = Ref { *this }, message = WTFMove(message)](ScriptExecutionContext& ctx) mutable {
// Using WTFMove(message) inside the lambda
});
// Incorrect: unnecessary mutable
context->postImmediateCppTask([protectedThis = Ref { *this }, message](ScriptExecutionContext& ctx) mutable {
// No modification of captured variables
});
Similarly, avoid redundant moves on function objects unless there’s a specific rvalue ref-qualified overload of operator() you intend to call:
// Correct: Direct invocation
completionCallback();
// Unnecessary: redundant move
WTFMove(completionCallback)();
Using move operations judiciously improves code clarity and helps avoid subtle bugs related to object ownership.
Network code must validate all inputs and handle error conditions properly to prevent security vulnerabilities and reliability issues. This includes:
Example for select loops:
while (true) {
// Reset state at beginning of each iteration
FD_ZERO(&read_set);
FD_SET(kqueue_fd, &read_set);
for (size_t i = 0; i < fds_len; i++) {
FD_SET(fds[i], &read_set);
}
int rv = select(max_fd + 1, &read_set, NULL, NULL, NULL);
// Handle errors...
}
Example for type conversions in protocol handling:
// Use safe conversion with error handling
llhttp_type_t type = static_cast<llhttp_type_t>(typeValue.toNumber(globalObject));
RETURN_IF_EXCEPTION(scope, {});
Failing to properly validate network inputs can lead to subtle bugs like missed events in event loops, crashed servers due to unexpected input formats, or even security vulnerabilities that could be exploited by malicious actors.
Implement defensive checks when managing error state to prevent overwriting existing errors and add safeguards even when theoretically unnecessary. This approach helps avoid subtle bugs and makes code more robust against future modifications.
Key practices:
Example from the codebase:
// Before: Risk of overwriting existing error
if self.readMore() {
goto try_skip
} else {
err = SyntaxError{e, self.s, types.ParsingError(-s), ""}
self.setErr(err)
return
}
// After: Defensive check prevents overwriting
if self.readMore() {
goto try_skip
}
if self.err == nil {
self.setErr(SyntaxError{e, self.s, types.ParsingError(-s), ""})
}
return self.err
This defensive approach prevents losing important error context and makes the code more maintainable as it evolves.
Choose specific, descriptive names that clearly convey purpose over generic or abbreviated identifiers. Names should be self-documenting and unambiguous about their role.
Key guidelines:
Example:
// Problematic - generic, unclear names
private int num;
private boolean type;
private String content;
// Better - specific, descriptive names
private int componentCount;
private boolean lastInFirstOutOrdering;
private String contentHeaderValue;
This practice improves code readability, reduces the need for additional documentation, and helps prevent misunderstandings during maintenance. When choosing names, consider whether another developer could understand the purpose without additional context.
Ensure API implementations strictly adhere to published specifications, even when adding features or optimizations. Deviating from standard behavior can create compatibility issues with other implementations (like Node.js) that rely on specification-compliant behavior.
When implementing web APIs:
Example:
// INCORRECT: Adding non-standard behavior directly in core API implementation
void MessagePort::transfer() {
// This behavior differs from spec - ports shouldn't be "closed" on transfer
dispatchCloseEvent();
}
// CORRECT: Follow specification behavior
void MessagePort::transfer() {
detachWithoutClosing(); // Aligns with spec: port is detached but not closed
}
// Alternative for compatibility needs
namespace CompatLayer {
void handleMessagePortTransfer(MessagePort* port) {
// Add Node.js specific behavior here if needed
}
}
Choose names that clearly communicate the semantic purpose and behavior of code elements. Variable and function names should precisely reflect their role, return type, and intended usage.
Key guidelines:
Example:
// Poor naming
function isGlobalType(checker: TypeChecker, symbol: Symbol) {
return checker.resolveName(symbol.name, undefined, SymbolFlags.Type, false);
}
// Better naming - reflects actual behavior
function canResolveTypeGlobally(checker: TypeChecker, typeName: string) {
return checker.resolveName(typeName, undefined, SymbolFlags.Type, false);
}
// Poor naming - reusing variable
let initializer = getCandidateVariableDeclarationInitializer(declaration);
if (initializer) { ... }
initializer = getDestructuredInitializer(declaration);
// Better naming - distinct purposes
let directInit = getCandidateVariableDeclarationInitializer(declaration);
if (directInit) { ... }
let destructuredInit = getDestructuredInitializer(declaration);
Maintain clean code organization by moving implementation details to appropriate locations, extracting reusable functionality, and keeping related code together. This improves maintainability and prevents bugs from poor encapsulation.
Key practices:
bundle/esbuild.rs)deno_core)Example of good organization:
// Instead of inline implementation details
pub async fn bundle() -> Result<(), AnyError> {
// ... lots of esbuild setup code ...
}
// Extract to dedicated module
// bundle/esbuild.rs
pub async fn ensure_esbuild(...) -> Result<PathBuf, AnyError> {
// esbuild-specific logic here
}
// bundle/mod.rs
pub async fn bundle() -> Result<(), AnyError> {
let esbuild_path = esbuild::ensure_esbuild(...).await?;
// ... main bundling logic ...
}
This approach makes code easier to test, maintain, and understand by grouping related functionality and separating concerns.
Add clear comments explaining the purpose and reasoning behind complex or non-obvious code logic. Comments should explain ‘why’ certain implementation choices were made or how specific variables and conditions function, rather than just restating what the code does. This is especially important for security-related checks, timing mechanisms, interruption handling, and specialized attribute usage.
For example:
/* Tracks the starting time of the wait loop to calculate the remaining timeout
* when re-entering the loop after an interruption. */
DWORD start_ticks = GetTickCount();
// OR
/* Skip inlining methods requiring security objects to prevent
* security context bypass vulnerabilities */
if (target_method->flags & METHOD_ATTRIBUTE_REQSECOBJ) {
return FALSE;
}
Use predefined utility macros for common operations instead of repeating manual calculations throughout your code. This improves readability, consistency, and reduces the likelihood of errors in calculations.
For example, instead of manually calculating array sizes:
// Don't do this
if (wcsncmp(path, DevicePathPrefix, sizeof(DevicePathPrefix) / sizeof(WCHAR) - 1) == 0)
return NULL;
// Do this instead
if (wcsncmp(path, DevicePathPrefix, STRING_SIZE(DevicePathPrefix) - 1) == 0)
return NULL;
Check for existing utility macros in your project (such as ARRAY_SIZE, STRING_SIZE in util.h) before writing common calculations. This practice ensures consistent implementation across the codebase and makes future maintenance easier by centralizing any changes needed to these calculation patterns.
Functions should use specific, appropriate error handling mechanisms rather than generic or brittle approaches. This includes returning proper error codes when operations fail, using specific error types instead of generic notImplemented errors, and avoiding fragile error detection methods like string matching.
Key practices:
e.message === "cancelled" - prefer proper error type checkingExample of improvement:
// Instead of always returning success:
setBroadcast(_bool: 0 | 1): number {
this.#listener?.setBroadcast(_bool === 1);
return 0; // Always success - problematic
}
// Return appropriate error codes:
setBroadcast(_bool: 0 | 1): number {
try {
this.#listener?.setBroadcast(_bool === 1);
return 0;
} catch {
return -1; // Proper error indication
}
}
// Instead of generic errors:
export function setFipsCrypto(_fips: boolean) {
notImplemented("crypto.setFipsCrypto"); // Generic
}
// Use specific error types:
export function setFipsCrypto(_fips: boolean) {
throw new Error("FIPS mode is not available"); // Specific
}
Extract hard-coded configuration values into variables, parameters, or templates to improve reusability and simplify maintenance. When the same value is used in multiple places or might need to change in the future, define it once and reference it throughout your configuration files.
Examples:
variables: timeoutPerTestCollection: 180
jobParameters: timeoutInMinutes: ${{ variables.timeoutPerTestCollection }}
This practice reduces duplication, centralizes configuration management, and makes future updates more efficient and less error-prone.
---
## remove unnecessary prefixes
<!-- source: denoland/deno | topic: Code Style | language: Css | updated: 2025-05-29 -->
Remove vendor prefixes from CSS properties that have achieved sufficient browser support across modern browsers. Before removing prefixes, verify browser compatibility using resources like MDN documentation to ensure the unprefixed property is well-supported.
This practice improves code readability, reduces maintenance overhead, and eliminates redundant code. Focus on commonly supported properties that no longer require vendor-specific implementations.
Example cleanup:
```css
/* Before - unnecessary prefixes */
*:before {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
pre {
-moz-tab-size: 2;
-o-tab-size: 2;
tab-size: 2;
}
/* After - clean, modern CSS */
*:before {
box-sizing: border-box;
}
pre {
tab-size: 2;
}
When implementing networking APIs that mirror Node.js functionality, ensure both behavior and error patterns match Node.js exactly. This is critical for maintaining compatibility with the Node.js ecosystem and ensuring that code works consistently across both environments.
Key areas to check for compatibility include:
send(2) rather than sendto(2) for TCP sockets)For example, when validating TLS ciphers, ensure the logic matches Node.js behavior by only throwing when no ciphers match (not when any individual cipher is unsupported):
// INCORRECT - throws if any cipher isn't supported
const requested = options.ciphers.split(":");
for (const r of requested) {
if (!DEFAULT_CIPHERS_SET.has(r)) {
throw $ERR_SSL_NO_CIPHER_MATCH();
}
}
// CORRECT - matches Node.js behavior by only throwing if no ciphers match
const requested = options.ciphers.split(":");
let hasMatch = false;
for (const r of requested) {
if (DEFAULT_CIPHERS_SET.has(r)) {
hasMatch = true;
break;
}
}
if (!hasMatch) {
throw $ERR_SSL_NO_CIPHER_MATCH();
}
Always reference the Node.js implementation when implementing networking APIs to ensure consistent behavior across runtime environments.
When modifying shared state in multithreaded code, implement proper checks and state transitions to prevent race conditions. This applies to flags, counters, and resource management.
Bad:
// Don't check flags without thread safety
if (m_terminationFlags & TerminatedFlag) {
// Already terminated, skip work
return;
}
// Race condition: Another thread might have set the flag between check and action
Good:
// Use atomic operations and check the return value
if (atomic_fetch_or(&m_terminationFlags, TerminatedFlag) & TerminatedFlag) {
// Already terminated, skip work
return;
}
// For multi-step processes, track completion:
protectedThis->m_messagesInFlight -= 1;
if (protectedThis->m_messagesInFlight == 0)
protectedThis->close(); // Only close when all messages are processed
Always provide configurable options for performance instrumentation and hardware acceleration in your codebase. These options should be clearly documented and defaulted appropriately for common scenarios.
For hardware optimizations:
// Good example:
RETAIL_CONFIG_DWORD_INFO(EXTERNAL_EnableAVXVNNIINT8, W("EnableAVXVNNIINT8"), 1, "Allows AVXVNNI8+ hardware intrinsics to be disabled")
For performance measurement:
<NestedBuildProperty Include="WasmEnableEventPipe" />
<NestedBuildProperty Include="WasmPerformanceInstrumentation" />
Implementing configurable performance options allows teams to make data-driven optimization decisions and tailor application behavior to specific deployment environments.
Ensure dependency declarations in build configuration files use concrete values rather than variables that may not resolve properly at build time. Maintain consistency between related configuration files (like BOM and aggregator POMs) to prevent build failures.
When declaring dependencies in Maven POM files:
${os.detected.classifier})requireSameVersions rule for critical dependenciesExample of problematic configuration:
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>${tcnative.artifactId}</artifactId>
<classifier>${tcnative.classifier}</classifier>
</dependency>
This can result in errors like:
Could not find netty-tcnative-2.0.70.Final-${os.detected.classifier}.jar
Instead, use concrete values or handle platform-specific dependencies using proper Maven features like profiles or separate platform-specific modules.
When documenting or implementing configuration options, always include variations needed for different environments and use cases. Ensure that your configuration files, installation guides, and rule definitions cover all common scenarios.
For installation instructions, include platform-specific commands and hardware variations:
# Standard installation
winget install Oven-sh.Bun
# For CPUs without AVX2 instruction set support
winget install Oven-sh.Bun.Baseline
For rule definitions and pattern matching, include all relevant files:
globs: *.ts, *.tsx, *.html, *.css, *.js, *.jsx, package.json
This ensures users can successfully set up and use the system regardless of their specific environment, and automation tools will properly process all configuration files.
Write code that clearly expresses intent through explicit patterns, simplified logic, and readable structure. Avoid unnecessary complexity that obscures the code’s purpose.
Key practices:
if condition { return true } else { return false } with return condition_ = function_call() instead of just function_call() when intentionally ignoring return valuesExample of improvement:
// Before: verbose and nested
pub fn (s Status) is_success() bool {
if s.status.is_success() {
return true
} else {
return false
}
}
// After: clear and direct
pub fn (s Status) is_success() bool {
return s.status.is_success()
}
This approach makes code easier to read, understand, and maintain by reducing cognitive load and making intentions explicit.
Ensure configuration examples and documentation work across different operating systems and clearly distinguish between required and optional configuration steps. Avoid platform-specific syntax that may not work universally, and explicitly state when configuration steps are optional versus mandatory.
When providing path examples in configuration files, use universally supported formats:
{
"parser-directories": [
"~/src",
"$HOME/dev",
"/Users/my-name/code"
]
}
Use plain ~ instead of ~username syntax, as the latter doesn’t work on all systems. For optional build or setup steps, clearly indicate their optional nature and explain the implications of skipping them:
Optionally, build the WASM library. If you skip this step, then the `tree-sitter web-ui` command will require an internet connection.
This prevents developers from encountering platform-specific failures and helps them make informed decisions about which configuration steps to include in their setup.
When implementing low-level synchronization mechanisms in multi-threaded code, ensure that memory barriers are correctly paired between reader and writer operations. For each read barrier, implement a corresponding write barrier to maintain memory consistency and prevent race conditions.
For example, in ARM64 assembly:
// Read side
dmb ishld // Data memory barrier for load operations
// Write side (corresponding barrier needed)
dmb ishst // Data memory barrier for store operations
Consider whether specialized atomic instructions (like ldar for loads or stlr for stores) might provide the same memory ordering guarantees with potentially better performance in specific scenarios. However, ensure these are implemented at the correct locations in the code to properly maintain the synchronization semantics.
Design feature flags to minimize dependency bloat while maximizing flexibility for users. Each optional dependency should be tied to a specific feature flag, allowing users to enable only the functionality they need.
Key practices:
For example, instead of requiring a dependency for all users:
# Avoid this approach - forces dependency on all users
rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
Use a more targeted approach:
# Better approach - separate optional functionality
rt = ["tokio/rt", "tokio/sync", "futures-util"]
join-map = ["rt", "hashbrown"]
For platform-specific features, use appropriate conditional compilation:
# For platform-specific features
[target.'cfg(all(target_os = "linux", feature = "rt"))'.dependencies]
# Linux-specific dependencies here
When organizing features, aim for a balance between granularity and usability, with a “full” feature that enables everything for convenience.
When evolving APIs in minor versions, always maintain backward compatibility to avoid breaking client code. Follow these principles:
// AVOID (breaking change):
public interface EventExecutorGroup {
long submittedTasks(); // New abstract method breaks implementing classes
}
// BETTER:
public interface EventExecutorGroup {
// Default implementation preserves compatibility
default long submittedTasks() {
return -1; // Indicates metric not supported
}
}
// AVOID (breaking change):
public abstract String readString(int length, Charset charset);
// BETTER:
public String readString(int length, Charset charset) {
String string = toString(readerIndex(), length, charset);
skipBytes(length);
return string;
}
@Deprecated
public static ZlibDecoder newZlibDecoder() {
return newZlibDecoder(0); // Forward to newer method
}
public static ZlibDecoder newZlibDecoder(int maxAllocation) {
// New implementation with additional parameter
}
Following these principles ensures your API evolves cleanly without disrupting existing users, allowing them to migrate at their own pace.
Keep configuration flags, feature toggles, and build settings concise and well-organized. Use simpler names where appropriate and consolidate related flags when possible to improve readability and maintainability.
When defining new configuration flags:
Example of simplifying multiple flags:
# Before: Multiple separate flags
- RUSTFLAGS: -Dwarnings --check-cfg=cfg(loom) --check-cfg=cfg(tokio_unstable) --check-cfg=cfg(tokio_taskdump) --check-cfg=cfg(fuzzing)
# After: Consolidated flags
+ RUSTFLAGS: -Dwarnings --check-cfg=cfg(loom, tokio_unstable, tokio_taskdump, fuzzing)
For feature flags and conditional compilation:
# Before: Verbose, redundant naming
- --cfg tokio_unstable_uring
# After: Simplified naming
+ --cfg tokio_uring
This approach makes configuration more maintainable, reduces verbosity, and improves readability, especially as the number of configuration options grows over time.
When implementing Pointer Authentication (PAC) for security, maintain signed pointers throughout their entire lifecycle to prevent potential security vulnerabilities. Avoid unnecessarily stripping PAC signatures from pointers that will be stored and later reused. This preserves the security guarantees that PAC is designed to provide.
For example, when storing a return address:
// AVOID: Stripping signature before storage creates a security hole
m_pvHJRetAddr = PacStripPtr(m_pvHJRetAddr);
// BETTER: Store the signed pointer to maintain protection
m_pvHJRetAddr = originalSignedPointer;
// Only strip when absolutely necessary for processing
void* strippedForProcessing = PacStripPtr(m_pvHJRetAddr);
This approach ensures that any attempt to manipulate the stored address would fail authentication when the pointer is eventually used, significantly reducing the attack surface against return-oriented programming (ROP) attacks. When you do need to strip the signature for processing, limit the scope of the unsigned pointer and avoid storing it in a longer-lived variable or memory location.
Always verify that pointers and function return values are not null before dereferencing or using them. This prevents crashes and undefined behavior, especially when working with C functions or manual memory management.
Key practices:
display := C.XOpenDisplay(0) should be followed by a null check since XOpenDisplay is documented to return null on failureassert green_arena.data != unsafe { nil } and assert green_arena.head != unsafe { nil }if g.fn_decl == unsafe { nil } || g.fn_decl.return_type == ast.void_type prevents crashes when fn_decl might be uninitializedcurrent.next = unsafe { nil } after free(current)Example from discussions:
// Bad: No null check for C function return
display := C.XOpenDisplay(0)
// Immediate use without verification
// Good: Check for null return
display := C.XOpenDisplay(0)
if display == unsafe { nil } {
return error('Failed to open display')
}
defer { C.XCloseDisplay(display) }
This practice is essential for robust code that interfaces with C libraries or manages pointers manually.
Choose names that clearly communicate the purpose, functionality, and usage context of variables, methods, and types. Avoid ambiguous or overly generic names that require additional context to understand.
When naming:
readWithCancelHandle instead of a generic property)Example from codebase:
// Instead of unclear property access:
if (this[kStreamBaseField]![_readCancel]) {
// Use descriptive method name:
if (this[kStreamBaseField]!.readWithCancelHandle()) {
Good naming reduces the need for comments and makes code self-documenting, improving maintainability and reducing cognitive load for other developers.
Always enable HTTP header validation to prevent request/response splitting vulnerabilities. These attacks can occur when untrusted data containing CRLF sequences is inserted into HTTP headers, allowing attackers to inject additional headers or even response body content.
When creating HTTP headers in Netty, avoid disabling the validation parameter:
// INSECURE: Disabled header validation
HttpHeaders inHeaders = new DefaultHttpHeaders(false);
// SECURE: Keep validation enabled (default)
HttpHeaders inHeaders = new DefaultHttpHeaders();
// or explicitly
HttpHeaders inHeaders = new DefaultHttpHeaders(true);
Even in test code, prefer to use valid headers rather than disabling validation. If you absolutely must test with invalid headers, isolate this code and clearly document the security implications with comments.
Request/response splitting attacks can lead to cache poisoning, XSS, and session fixation. Modern browsers and servers have some protections against these attacks, but proper header validation remains an important security defense-in-depth measure.
When implementing cryptographic algorithms, always verify the exact behavior and return values of security-critical functions. Misinterpreting how functions signal success or equality can lead to severe security vulnerabilities.
In particular:
Example of a potential error in a security check:
// INCORRECT: Misinterpreting constantTimeMemcmp which returns 0 for equality
if (derivedKey->size() != expectedOutputSize || !constantTimeMemcmp(derivedKey->span(), zeros)) {
// Handle insecure all-zeros key case
}
// CORRECT: Properly checking for equality with constantTimeMemcmp
if (derivedKey->size() != expectedOutputSize || constantTimeMemcmp(derivedKey->span(), zeros) == 0) {
// Handle insecure all-zeros key case
}
Security-critical checks must be implemented correctly the first time - cryptographic vulnerabilities can be subtle and devastating.
Always consider the time and space complexity implications of your code. Choose data structures and algorithms that minimize computational overhead and memory usage for your specific use cases.
Watch for hidden complexity issues:
Avoid nested iterations that create O(n²) behavior
// Poor: Iterates through all buffers on each call, potentially O(n²) in a loop
let n = bufs.iter().map(|b| b.len()).sum::<usize>().min(MAX_BUF);
// Better: Calculate incrementally inside a loop to maintain O(n) complexity
let mut remaining = MAX_BUF;
let mut total = 0;
for buf in bufs {
let to_copy = buf.len().min(remaining);
total += to_copy;
remaining -= to_copy;
if remaining == 0 {
break;
}
}
Choose efficient operations over expensive ones
// Poor: Using power operation which is more expensive
let max_number = 2u64.saturating_pow((8 * length_field_len - 1) as u32);
// Better: Using bit shift which is much faster
let max_number = match 1.checked_shl(8 * length_field_len) {
Some(shl) => shl - 1,
None => u64::MAX,
};
Design for fairness when consuming from multiple sources
// When polling multiple streams, implement rotation strategies to ensure
// fair processing and prevent starvation:
let mut added = 0;
let mut start = thread_rng_n(entries.len() as u32) as usize;
while added < limit {
match self.poll_one(cx, start) {
Poll::Ready(Some((idx, val))) => {
added += 1;
// Move the start index to ensure fair rotation
start = idx.wrapping_add(1) % entries.len();
// Process val...
}
_ => break,
}
}
Use iterators over index-based access when appropriate
// Poor: Using index-based access in a loop
let expiration_time = (0..wheel_count)
.filter_map(|id| wheel.get_sharded_wheel_mut(id).next_expiration_time())
.min();
// Better: Using iterator methods directly on the collection
let expiration_time = wheels.iter_mut()
.filter_map(|wheel| wheel.next_expiration_time())
.min();
Avoid code duplication that leads to excessive monomorphization
// Poor: Duplicating implementation for each function call type
pub fn with_func1(cmd: &mut Cmd, with: impl Fn(&mut Cmd) -> Result<Child>) -> Result<Child> {
// Duplicated implementation
}
// Better: Extract common logic to avoid recompiling the same code
pub fn with_func(cmd: &mut Cmd, with: impl Fn(&mut Cmd) -> Result<Child>) -> Result<Child> {
handle_common(&mut with(cmd)?)
}
fn handle_common(child: &mut Child) -> Result<Child> {
// Common implementation that won't be duplicated
}
By paying attention to these efficiency concerns, you’ll produce code that scales better and uses resources more efficiently.
When dealing with Optional values, prefer Rust’s built-in Option methods over manual null checks. This makes code more concise, self-documenting, and less error-prone by leveraging Rust’s type system to enforce null safety at compile time.
For optional value checking:
// Instead of:
if self.processing_scheduled_tasks_started_at.is_some() {
let busy_duration = self.processing_scheduled_tasks_started_at.unwrap().elapsed();
// Use busy_duration...
}
// Use:
if let Some(processing_scheduled_tasks_started_at) = self.processing_scheduled_tasks_started_at {
let busy_duration = processing_scheduled_tasks_started_at.elapsed();
// Use busy_duration...
}
For lazy initialization:
// Instead of:
if me.entry.is_none() {
let entry = TimerEntry::new(me.handle, deadline);
me.entry.as_mut().set(Some(entry));
}
// Use:
let entry = me.entry.get_or_insert_with(|| TimerEntry::new(me.handle, deadline));
For error handling with Option:
// Instead of:
let n = match u32::try_from(n) {
Ok(n) => n,
Err(_) => return None,
};
// Use:
let n = u32::try_from(n).ok()?;
Break down large test cases into smaller, focused tests that each verify a single feature or behavior. Each test should have a clear purpose that is evident from its name and structure. This improves test maintainability, readability, and makes failures easier to debug.
Instead of combining multiple test cases:
#[test]
fn test_channel_behavior() {
let (tx, rx) = channel();
// Test multiple behaviors...
assert!(rx.is_empty());
tx.send(1).unwrap();
assert!(!rx.is_empty());
drop(tx);
assert!(rx.is_closed());
}
Break it into focused tests:
#[test]
fn is_empty_returns_true_initially() {
let (_, rx) = channel();
assert!(rx.is_empty());
}
#[test]
fn is_empty_returns_false_after_send() {
let (tx, rx) = channel();
tx.send(1).unwrap();
assert!(!rx.is_empty());
}
#[test]
fn is_closed_returns_true_after_sender_drop() {
let (tx, rx) = channel();
drop(tx);
assert!(rx.is_closed());
}
Key benefits:
When writing shell scripts, set and export all environment variables at the top of the script rather than modifying them repeatedly throughout the script. This improves readability, maintainability, and prevents inconsistent environments between commands.
For tools like Node.js or npm, prefer using local installations over globally installed versions by explicitly setting paths to include local tool directories:
# Instead of:
NODEPATH="$(../$NODE -p 'require("path").resolve("..")')"
PATH="$NODEPATH:$PATH" ../$NODE cli.js install --ignore-scripts
PATH="$NODEPATH:$PATH" ../$NODE test/run.js
# Do this:
NODEPATH="$(../$NODE -p 'require("path").resolve("..")')"
export PATH="$NODEPATH:$PATH"
../$NODE cli.js install --ignore-scripts
../$NODE test/run.js
# Or even better, if appropriate:
export PATH="$(../$NODE -p 'require("path").resolve("..")'):$PATH"
../$NODE cli.js install --ignore-scripts
../$NODE test/run.js
This approach ensures consistent access to required tools across the entire script and makes changes easier to implement when needed.
Make configuration parameters configurable rather than hardcoding values, especially for limits, sizes, and thresholds. Provide reasonable defaults but allow users to override through:
For related configuration parameters, design APIs that ensure they are set cohesively to prevent invalid states:
// Prefer this:
public IoUringIoHandlerConfig setSize(int ringSize, int cqSize) {
// validate interdependent parameters together
return this;
}
// Over separate methods that could create invalid configurations:
public IoUringIoHandlerConfig setRingSize(int ringSize) { ... }
public IoUringIoHandlerConfig setCqSize(int cqSize) { ... }
When exposing configuration capabilities, make feature detection methods public to help users adapt to different environments:
if (IoUring.isRecvMultishotEnabled()) {
// Use optimized configuration
}
Size-related parameters should be derived from existing configurations when reasonable rather than using arbitrary hardcoded values.
When designing APIs and handling potentially missing or unavailable values, prefer returning optional types (Option
Key practices:
None instead of using unreachable!() for cases that might become reachable in the futureOption<T> for platform-specific values that may not be available everywhereunwrap() which can panicExample from the discussions:
// Instead of this (can panic if plugin changes):
_ => unreachable!(),
// Prefer this (safe fallback):
_ => None,
// For platform-specific values:
// Instead of: Result<(ResourceId, IpAddr, IpAddr, Fd), NetError>
// Use: Result<(ResourceId, IpAddr, IpAddr, Option<Fd>), NetError>
// Handle errors instead of panicking:
// Instead of: parsed_source.specifier().to_file_path().unwrap()
// Use proper error handling with Result types
This approach makes code more robust, prevents runtime panics, and clearly communicates when values might be absent through the type system.
Ensure that all functionality, especially complex or quirky features, has corresponding unit tests. When adding new features or modifying existing code, proactively identify areas that lack test coverage and add appropriate tests.
This is particularly important for:
For example, when adding flag parsing functionality:
#[test]
fn test_clean_command_parsing() {
// Test basic clean command
let mut flags = Flags::default();
let matches = /* create test matches */;
clean_parse(&mut flags, &matches);
assert_eq!(flags.subcommand, DenoSubcommand::Clean);
}
#[test]
fn test_clean_command_with_options() {
// Test clean command with various options
// ...
}
Or for utility functions:
#[test]
fn test_url_to_uri_conversion() {
// Test various URL formats
assert_eq!(url_to_uri(&url).unwrap(), expected_uri);
}
Don’t assume that functionality is “too simple” to test - utility functions, parsing logic, and similar features across a codebase should all have comprehensive test coverage to prevent regressions and ensure correctness.
When implementing functionality, carefully evaluate different algorithmic approaches and data structures to choose the most appropriate solution. Consider factors like performance requirements, correctness, maintainability, and whether existing libraries can be leveraged instead of custom implementations.
Key considerations:
deno_semver::Version for version parsing instead of custom parsing)Example from the codebase:
// Instead of custom version parsing:
struct VersionParts {
major: u64,
minor: u64,
patch: u64,
pre: Option<String>,
}
// Consider using existing library:
// Should we be using `deno_semver::Version` here?
use deno_semver::Version;
This approach reduces bugs, improves maintainability, and often provides better performance than ad-hoc implementations.
All public-facing APIs must be thoroughly documented with clear javadocs. This includes:
Classes and interfaces: When creating or changing a class/interface to public visibility, add comprehensive documentation explaining its purpose and usage.
@deprecated tags with explanations of what alternatives users should use instead.Example of proper documentation:
/**
* Decodes DNS response messages into {@link DnsResponse} objects.
* <p>
* This decoder supports both UDP and TCP transport protocols for DNS messages.
* When using TCP, DNS messages are prefixed with a 2-byte length field.
*
* @param <A> The type of address used by the decoder (e.g., {@link java.net.InetSocketAddress})
*/
public abstract class DnsResponseDecoder<A extends SocketAddress> {
/**
* Decodes a DNS response message with the specified maximum allocation limit.
*
* @param maxAllocation The maximum allowed memory allocation per message in bytes.
* A value of 0 means no limit.
* @return A {@link DnsDecoder} instance with the specified allocation limit
* @throws IllegalArgumentException if maxAllocation is negative
*/
public static DnsDecoder newDnsDecoder(int maxAllocation) {
// implementation
}
/**
* @deprecated Use {@link #newDnsDecoder(int)} instead with an appropriate allocation limit
*/
@Deprecated
public static DnsDecoder newDnsDecoder() {
return newDnsDecoder(0);
}
}
Remember that clear documentation helps maintainers understand code intent, improves API usability for consumers, and prevents future bugs from misunderstandings about component behavior.
When modifying configuration files (tsconfig.json, environment configs, build settings, etc.), always provide clear explanations for why the changes are necessary, what benefits they provide, and any potential impacts. Configuration changes can significantly affect build processes, development workflows, and application behavior, so reviewers and future maintainers need to understand the reasoning behind modifications.
Include explanations either in:
Example from TypeScript configuration:
{
"compilerOptions": {
// Enable composite mode for faster incremental builds with tsc --build
"composite": true,
// Ensure each file can be transpiled independently for bundlers like esbuild
"isolatedModules": true
},
"include": [
"src/**/*",
// Include lib directory to ensure web-tree-sitter.d.ts is type-checked during builds
"lib/**/*.ts"
]
}
This practice helps prevent confusion during code reviews and provides valuable context for future configuration maintenance.
Network APIs and interfaces should include comprehensive documentation with clear descriptions, practical examples, and proper configuration guidance. This is especially important for complex protocols like WebSockets, HTTPS, and specialized network transports.
When documenting network APIs:
Example of comprehensive network API documentation:
/**
* Returns the extensions selected by the server, if any.
*
* WebSocket extensions add optional features negotiated during the handshake via
* the `Sec-WebSocket-Extensions` header.
*
* At the time of writing, there are two registered extensions:
* - `permessage-deflate`: Enables per-message compression using DEFLATE
* - `bbf-usp-protocol`: Used by the Broadband Forum's User Services Platform
*
* Example:
* ```ts
* const ws = new WebSocket("ws://localhost:8080");
* console.log(ws.extensions); // e.g., "permessage-deflate"
* ```
*/
readonly extensions: string;
This approach helps developers understand not just how to use network APIs, but also the underlying protocols and proper configuration patterns.
Feature flags should be designed with granularity to ensure dependencies are only required when absolutely necessary. When adding functionality that requires additional dependencies, create separate features rather than adding those dependencies to existing features.
Key principles:
Example:
# GOOD: Granular feature with minimal dependencies
[features]
rt = ["tokio/rt", "tokio/sync", "futures-util"]
join-map = ["rt", "hashbrown"] # Separate feature for hashbrown dependency
# BAD: Requiring hashbrown for all rt users
[features]
rt = ["tokio/rt", "tokio/sync", "futures-util", "hashbrown"]
This approach keeps the dependency tree minimal for users who don’t need specific functionality, improving compile times and reducing potential version conflicts. When considering target-specific configurations, ensure dependencies are properly scoped to the relevant target and features (e.g., #[cfg(all(target_os = "linux", feature = "rt"))]).
Cache frequently accessed or computed values to avoid redundant calculations and property lookups. This applies to:
Example - Before:
while (index < path.length) {
// path.length accessed on every iteration
if (path.indexOf(separator, index) !== -1) {
// indexOf called multiple times
}
}
Example - After:
const pathLength = path.length; // Cache loop invariant
const separatorIndex = path.indexOf(separator, index); // Cache computation
while (index < pathLength) {
if (separatorIndex !== -1) {
// Use cached values
}
}
This optimization is particularly important in performance-critical paths and loops. When implementing, consider:
The performance impact can be significant, especially when the cached values are used frequently or the computations are expensive.
Always prefer established API interfaces and patterns over custom or alternative approaches. This ensures compatibility, maintainability, and follows expected conventions.
For module imports, use standard ESM syntax instead of dynamic loading:
// Preferred
import { assert } from "node:assert";
// Avoid
const assert = process.getBuiltinModule("node:assert");
For server interfaces, follow the established contract structure:
// Preferred
export default {
fetch(req: Request) {
return new Response("Hello from declarative server");
},
onListen(info) {
console.log(info);
}
} satisfies Deno.ServeDefaultExport;
// Avoid
export const fetch = (req: Request) => {
return new Response("Hello from declarative server!");
};
This approach ensures your code works with existing tooling, follows documented patterns, and reduces the risk of breaking changes when alternative methods are deprecated.
Focus on reducing memory footprint and improving cache efficiency through careful data structure design and function parameter optimization. Consider using smaller data types when the value range permits, and design function interfaces to minimize memory overhead.
Key strategies include:
uint8_t instead of int when values fit in 8 bits)Example: Instead of using Array(TSQuantifier) which may use 4 bytes per element, consider Array(uint8_t) and cast to TSQuantifier when reading, reducing memory usage by 75% when the enum values fit in a byte. Similarly, prefer passing buffer parameters directly rather than relying on global transfer buffers to improve function interface efficiency.
Avoid code patterns that require workarounds for tooling limitations or create maintenance burdens. This includes using contextual keywords as identifiers that cause formatter edge cases, and relying on @ts-ignore comments to suppress type checking.
Instead of working around tooling issues, prefer patterns that work naturally with the development environment:
For example, instead of:
// Workaround: rename "from" to avoid formatter issues
const renameForDefaultExport = ["from"];
// @ts-ignore Undocumented function
someUndocumentedFunction();
Prefer:
// Use clear, non-conflicting names
const renameForDefaultExport = ["fromKeyword"];
// Properly type or restructure instead of ignoring
someDocumentedFunction();
This approach reduces technical debt, improves code maintainability, and prevents future issues when tooling is updated.
Pay careful attention to operations inside loops and recursive functions to avoid unexpected algorithmic complexity. Be particularly vigilant about:
// Poor performance: iterates through all buffers on each call
let n = bufs.iter().map(|b| b.len()).sum::<usize>().min(MAX_BUF);
// Better approach: avoid recomputing the total length repeatedly
let mut total = 0;
for buf in bufs {
total += buf.len();
if total >= MAX_BUF {
total = MAX_BUF;
break;
}
}
// Expensive calculation using power
let max_number = 2u64.saturating_pow((8 * length_field_len - 1) as u32);
max_number + (max_number - 1);
// More efficient using bit shift
let max_number = match 1.checked_shl(8 * length_field_len) {
Some(shl) => shl - 1,
None => u64::MAX,
}
// Separate logic into non-generic helper function
pub(crate) fn spawn_child_with(
cmd: &mut StdCommand,
with: impl Fn(&mut StdCommand) -> io::Result<StdChild>,
) -> io::Result<SpawnedChild> {
spawn_child(&mut with(cmd)?)
}
fn spawn_child(cmd: &mut StdChild) -> io::Result<SpawnedChild> {
// Common implementation that won't be duplicated
}
// Less efficient with multiple operations
if now > timeout.checked_add(Duration::from_millis(5)).unwrap_or_else(Instant::far_future) {
// Handle case
}
// More efficient using a specialized method
if now.saturating_duration_since(timeout) > Duration::from_millis(5) {
// Handle case
}
These optimizations matter most in hot code paths, libraries meant for wide adoption, or when handling large data structures.
Optimize performance by reducing the amount of computation performed, especially on data that won’t appear in the final result. Apply these strategies:
1) Filter early: Filter collections before performing expensive transformations like sorting
// Suboptimal: Sorts all elements, then removes private ones
functions.sortBy(KmFunction::name)
functions.removeIf { it.visibility.isPrivate }
// Optimized: Removes private elements first, then sorts only what's needed
functions.removeIf { it.visibility.isPrivate }
functions.sortBy(KmFunction::name)
2) Short-circuit common cases: Add early returns for special cases like empty collections or single elements
public fun <T> Array<out Array<out T>>.flatten(): List<T> {
if (isEmpty()) return emptyList() // Early return for common case
// Regular processing...
}
3) Cache expensive results: Use memoization for operations that may be called repeatedly with the same input
private val containsCache = mutableMapOf<IrDeclaration, Boolean>()
override fun containsDeclaration(declaration: IrDeclaration): Boolean =
containsCache.getOrPut(declaration) {
// Expensive computation here
}
4) Optimize for expected usage patterns: Special-case handle common scenarios based on benchmarks
// Optimization for common no-argument functions
if (parameters.isEmpty()) {
return reflectionCall {
caller.call(if (isSuspend) arrayOf(continuationArgument) else emptyArray()) as R
}
}
Always verify your optimizations with benchmarks to ensure they provide measurable benefits in real-world scenarios.
Ensure proper memory ordering in concurrent code by using appropriate memory barriers and atomic operations based on the access pattern. When implementing release-store semantics, place the store fence before the actual store to prevent instruction reordering:
// INCORRECT - barrier after store
value.store(newValue);
storeFence(); // Too late!
// CORRECT - barrier before store
storeFence(); // Prevents reordering of previous stores
value.store(newValue);
Key guidelines:
This approach prevents subtle race conditions while maintaining performance in concurrent systems.
Write tests that comprehensively verify functionality by covering related scenarios and testing complete outputs rather than partial matches. This approach makes tests more resilient to code changes and ensures thorough validation.
When testing a feature, consider testing related functionality that users might invoke in similar contexts. For example, if testing deno add --npm, also test deno install and related command variations.
Additionally, prefer testing complete outputs over partial matches using wildcards. Instead of testing only fragments, verify the full output to catch unintended changes:
// Less resilient - partial output testing
"output": "[WILDCARD]Found 1 problem"
// More resilient - complete output testing
"output": "[WILDCARD]Found 1 problem\nChecked 1 file"
This ensures tests catch when fixes or messages are moved, modified, or when output format changes unexpectedly.
Streamline code by simplifying control flow structures to improve readability. Eliminate unnecessary nesting and verbosity by applying these guidelines:
else blocks after return, break, continue, or throw statements:
```java
// Prefer this:
if (condition) {
return value;
}
return otherValue;// Over this: if (condition) { return value; } else { return otherValue; }
2. Use early returns to reduce nesting levels:
```java
// Prefer this:
if (condition1) {
// Handle special case
return;
}
// Handle normal case
// Over this:
if (condition1) {
// Handle special case
} else {
// Handle normal case
}
// Over this: String scheme = “http”; if(ctx.channel().pipeline().get(SslHandler.class) != null) { scheme = “https”; }
4. Follow consistent loop patterns used throughout the codebase:
```java
// Use this pattern for polling loops:
for (;;) {
Object msg = queue.poll();
if (msg == null) {
break;
}
pipeline.fireChannelRead(msg);
}
// Instead of while or other variations
Design APIs with flexibility and consistency by leveraging established patterns and avoiding unnecessary constraints. Accept generic type bounds instead of concrete types to provide more flexibility to users and simplify usage.
Be flexible with input types:
// Less flexible - requires exact String type
pub fn name(&mut self, name: String) -> &mut Self {
// ...
}
// More flexible - accepts any type that can be converted to String
pub fn name<S: Into<String>>(&mut self, name: S) -> &mut Self {
// ...
}
Follow established patterns in your codebase to ensure consistency across similar APIs. When you need to design a new component, look for similar existing components as inspiration, avoiding unnecessary reinvention. For instance, if other stream types provide a specific set of methods, your new stream type should follow similar patterns.
Avoid arbitrary restrictions that don’t align with similar methods. For example, if related methods allow zero values, a new method should also allow zero values unless there’s a compelling reason not to:
// Inconsistent - restricts n to be > 0 when other methods don't
pub fn detach(&mut self, n: usize) -> Result<SemaphorePermit<'_>, Error> {
if n == 0 || n >= self.permits {
return Err(Error::InvalidValue);
}
// ...
}
// Consistent - follows the same patterns as similar methods
pub fn detach(&mut self, n: usize) -> Result<SemaphorePermit<'_>, Error> {
if n > self.permits {
return Err(Error::InvalidValue);
}
// ...
}
Choose names that clearly communicate behavior and purpose. Method names like spawn_aborting might suggest immediate abortion rather than creating something that aborts on drop. Consider alternatives like spawn_with_abort_handle or make it a constructor method on a type that better explains its purpose.
When implementing observability features like tracing and monitoring, avoid exposing redundant information that can be inferred from existing data, and use appropriate abstraction levels rather than exposing internal implementation details through public APIs.
For tracing spans, don’t set status descriptions when the information can be derived from other attributes:
// Avoid redundant message when status code is sufficient
if (res.status >= 400) {
span.setAttribute("error.type", String(res.status));
span.setStatus({
code: 2, // Error
// Don't set message - can be inferred from http.response.status_code
});
}
For internal observability data like file descriptors, use private symbols or internal mechanisms rather than exposing them through public APIs:
// Use internal symbols instead of public API exposure
const fd = conn[INTERNAL_FD_SYMBOL]; // Good
// Rather than: conn.fd (public API exposure)
This approach maintains clean public interfaces while still providing necessary observability data through appropriate channels.
When working with async operations, carefully manage execution context and resource references across await boundaries to prevent context corruption and resource leaks.
For execution context, avoid patterns where context enters/exits span async boundaries, as context may not be properly restored after await points. Instead, structure code to contain async operations within the context scope:
// Problematic - context lost across await
if (TRACING_ENABLED) {
span = builtinTracer().startSpan(this.method, { kind: 2 });
context = enterSpan(span);
// await operations here lose context
}
// Better - use IIFE to contain async operations
const old = getAsyncContext();
try {
setAsyncContext('inside operation');
return (async () => {
await asyncOperation();
// context preserved here
})();
} finally {
setAsyncContext(old);
}
For resource management, distinguish between ongoing and future async operations when managing references. Use unref/ref patterns to handle current operations without affecting future ones:
// Unref ongoing reads but not future reads
if (this.unref) {
this.unref(); // Clear current operation references
this.ref(); // Re-establish for future operations
}
This prevents resource leaks while maintaining proper reference counting for subsequent async operations.
Always check for null or undefined values before performing operations on objects, accessing properties, or calling methods. This prevents runtime errors that occur when attempting to operate on null/undefined values.
When working with potentially nullable objects, add explicit null checks before accessing properties or calling methods:
// Before - can throw if response is null
if (typeof response === "object" && ReflectHas(response, "then")) {
// ...
}
// After - safe with null check
if (response !== null && typeof response === "object" && ReflectHas(response, "then")) {
// ...
}
Similarly, check for property existence before using them:
// Check if optional properties exist before use
if (span) {
span.recordException(err);
if (err.name) { // Check err.name exists before using
span.setAttribute("error.type", err.name);
}
}
This pattern is especially important when calling methods like Reflect.has(), Object.keys(), or accessing properties on objects that might be null or undefined. The small overhead of these checks prevents difficult-to-debug runtime errors.
Create clear, comprehensive, and properly structured code samples to document API usage. Follow these principles:
sample("samples.collections.Collections.Elements.find")
specialFor(CharSequences) {
sample("samples.text.Strings.find")
}
@Sample fun substringWithRange() { // Overload with range val str = “abcde” assertPrints(str.substring(0, 3), “abc”) assertPrints(str.substring(0, 0), “”) } ```
@Sample
fun lastIndexOf() {
val inputString = "Never ever give up"
val toFind = "ever"
// Basic usage from start
assertPrints(inputString.lastIndexOf(toFind), "6")
// With specific start position
assertPrints(inputString.lastIndexOf(toFind, 5), "6")
// Start position after all occurrences
assertFails { inputString.lastIndexOf(toFind, 10) } // No occurrence after position 10
}
assertPrints() which converts to readable documentation output
// This:
assertPrints(value, "stringRepresentation")
// Converts to:
println(value) // stringRepresentation
@sample tags to all relevant function variants, including expect/actual declarationsEnsure tests verify both basic functionality and edge cases. Tests that only cover the happy path can miss critical bugs in boundary conditions and special scenarios.
For each feature:
Example:
@Sample
fun contains() {
// Basic functionality
val string = "Kotlin 1.4.0"
assertPrints(string.contains("K"), "true")
assertPrints(string.contains("k"), "false")
// Edge cases
assertPrints("".contains(""), "true") // Empty receiver and parameter
assertPrints("Kotlin".contains(""), "true") // Empty parameter
assertPrints("".contains("Kotlin"), "false") // Empty receiver
assertPrints("Kotlin".contains("Kotlin"), "true") // Identical strings
assertPrints("Kotlin 2.0.0".contains("Kotlin"), "true") // Receiver contains parameter
assertPrints("Kotlin".contains("Kotlin 2.0.0"), "false") // Parameter contains receiver
}
Include specific tests for error conditions using explicit assertions rather than try/catch blocks:
// Incorrect: Test passes even if cast doesn't fail
try {
BNativeHeir as A.Companion
} catch (e: Exception) {
assertTrue(e is ClassCastException)
}
// Correct: Test only passes if cast fails with expected exception
assertFailsWith<ClassCastException> {
BNativeHeir as A.Companion
}
Comprehensive testing prevents code from “rotting” and ensures reliability across all usage scenarios.
Optimize performance-critical algorithms by using branch prediction hints to guide the CPU. Add UNLIKELY macros for error conditions, boundary checks, or exceptional cases that rarely evaluate to true, and LIKELY for common execution paths. This helps the CPU’s branch predictor make better decisions, reducing pipeline stalls and improving execution speed in hot loops and frequently called functions.
Example:
// Less optimized:
if (number_num < lower_num || number_num > upper_num) {
// Handle out-of-range case
}
// Optimized with branch prediction:
if (UNLIKELY(number_num < lower_num || number_num > upper_num)) {
// Handle out-of-range case
}
This technique is especially important in performance-critical algorithms where the same conditional is evaluated repeatedly and the outcome is predictable in most cases.
All public functions and methods must be documented with comments that v doc can understand. Documentation should start with the function name and clearly explain the function’s behavior and purpose. For static methods, explicitly identify them as such (e.g., “MethodName static method returns…”).
Comments should be informative and provide meaningful context rather than simply repeating variable or function names. Avoid comments that just expand on names without adding value - it’s better to have no comment than one that merely restates obvious information.
Example of proper function documentation:
// get_queryset returns a list of QuerySet objects from the database query
pub fn (db &DB) get_queryset(query string) ![]QuerySet {
// Time.new static method returns a time struct with the calculated Unix time
pub fn Time.new() Time {
For complex code sections, add explanatory comments that clarify the purpose and reasoning, especially when the logic is not immediately obvious from reading the code.
When configuring CMake builds, carefully select compiler and linker flags that optimize for performance while maintaining cross-platform compatibility. Performance-oriented flags can significantly impact runtime speed, binary size, and resource utilization, but must be applied conditionally based on the target platform and compiler.
Key practices:
DYNAMIC_EXECUTION=0 may slow down WebAssembly bindings)--gc-sections for GCC/Clang vs --dead_strip for AppleClang)CMAKE_POSITION_INDEPENDENT_CODE ON for better code generationExample of conditional linker optimization:
target_link_options(yoga PRIVATE
# Discard unused sections
$<$<CONFIG:RELEASE>:$<$<CXX_COMPILER_ID:Clang,GNU>:-Wl,--gc-sections>>
$<$<CONFIG:RELEASE>:$<$<CXX_COMPILER_ID:AppleClang>:-Wl,-dead_strip>>)
This approach ensures optimal performance across different build environments while avoiding platform-specific build failures.
Be deliberate about memory allocation patterns to improve performance. Implement these practices:
// Less efficient - may require multiple reallocations
let mut output = Vec::new();
// More efficient - single allocation of correct size
let mut output = Vec::with_capacity(self.len());
// Less efficient - discards existing content
buffer.clear();
buffer.extend(new_items);
// More efficient - preserves existing content when appropriate
buffer.extend(new_items);
// Less memory-efficient
name: Option<Vec<u8>>,
name_demangled: Option<String>,
// More memory-efficient
name: Option<Box<[u8]>>,
name_demangled: Option<Box<str>>,
// Less efficient - always reserves space
buffer.reserve(BLOCK_CAP);
// More efficient - only reserves when necessary
if buffer.len() == buffer.capacity() {
buffer.reserve(BLOCK_CAP);
}
These practices reduce memory pressure, improve cache locality, and minimize the overhead of memory management operations.
When working with unsafe code, follow these practices to minimize security vulnerabilities:
// BAD: Overly broad unsafe block
unsafe {
let tail_block = &mut *tail;
if tail_block.is_closed() { ... }
}
// GOOD: Minimal scope
let tail_block = unsafe { &mut *tail };
if tail_block.is_closed() { ... }
// SAFETY: comment above each unsafe block explaining why the operation is safe.
// SAFETY: The pointer is guaranteed to be valid because it comes from an Arc
// that we have exclusive access to via get_mut()
unsafe { Arc::get_mut(&mut arc).unwrap_unchecked() }
// BAD: Multiple unsafe operations in one block
unsafe {
let fd = syscall(SYS_pidfd_open, pid, PIDFD_NONBLOCK);
if fd == -1 {
let errno = *__errno_location();
// ...
}
}
// GOOD: Separate unsafe blocks
let fd = unsafe { syscall(SYS_pidfd_open, pid, PIDFD_NONBLOCK) };
if fd == -1 {
let errno = unsafe { *__errno_location() };
// ...
}
// BAD: Function with hidden unsafe requirements
pub fn read_from(&mut self, inner: &mut T) { ... }
// GOOD: Properly marked function
pub unsafe fn read_from(&mut self, inner: &mut T) { ... }
Following these practices helps prevent memory corruption vulnerabilities and makes unsafe code easier to audit and maintain.
When writing code that requires unsafe operations, follow these critical security practices:
// SAFETY: comment explaining why the operation is safeThese practices reduce the risk of memory safety issues and make code easier to audit for security vulnerabilities.
Example:
// BAD: Large unsafe block with multiple operations
unsafe {
let block = self.head.as_ref();
let tail_block = &mut *tail;
// More code...
}
// GOOD: Minimal scope with documentation
// SAFETY: The tail pointer is guaranteed to be valid because...
let tail_block = unsafe { &mut *tail };
// More code with safe operations...
By limiting the scope of unsafe code, you make it easier to verify its correctness and maintain memory safety guarantees.
Always distinguish between nil and empty values when handling nullable types, as they carry different semantic meanings and can affect program behavior. Nil represents the absence of a value, while empty represents a zero-length but initialized value.
This distinction is particularly critical in data serialization/deserialization, where maintaining compatibility with standard library behavior is essential. For slices, maps, and other reference types, ensure your code handles both states appropriately.
Example:
// Good: Properly distinguish between nil and empty slice
if slice == nil {
// Handle nil case - no slice allocated
return handleNilSlice()
} else if len(slice) == 0 {
// Handle empty case - slice allocated but no elements
return handleEmptySlice()
}
// Good: Use appropriate zero values
// For empty slice, use zerobase pointer (not nil) to match standard library
self.Emit("MOVQ", jit.Imm(_Zero_Base), jit.Ptr(_VP, 0))
This practice prevents subtle bugs, maintains type information, and ensures consistent behavior across different execution paths.
Prioritize graceful error handling over panicking by providing fallbacks and propagating rich context. When operations can fail:
// Prefer this: let pos = std.stream_position().unwrap_or(0);
2. For functions that may panic with assertions, add `#[track_caller]` to improve error diagnostics:
```rust
#[track_caller]
pub fn global_queue_interval(&mut self, val: u32) -> &mut Self {
assert!(val > 0, "global_queue_interval must be greater than 0");
// ...
}
When exposing errors, implement the full std::error::Error trait including the source() method to maintain the error chain.
In documentation examples, demonstrate proper error handling rather than ignoring or silencing errors: ```rust // Instead of ignoring errors: let _ = compress_data(reader).await;
// Show proper error handling: compress_data(reader).await?;
5. Consider providing configuration options for users to handle certain error conditions differently when appropriate.
---
## validate configuration schemas
<!-- source: denoland/deno | topic: Configurations | language: Json | updated: 2025-02-23 -->
Configuration schemas should be actively validated, well-documented, and kept up-to-date through automated testing. Use appropriate schema types (enums vs strings) based on whether values are constrained, provide comprehensive descriptions that include examples and supported formats, and implement automated tests to ensure schemas remain current with code changes.
For example, instead of a generic string type:
```json
"items": {
"type": "string"
}
Use descriptive schemas with proper validation:
"plugins": {
"type": "array",
"description": "UNSTABLE: List of plugins to load. These can be paths, npm or jsr specifiers",
"items": {
"oneOf": [
{"type": "string", "format": "uri"},
{"enum": ["known-plugin-1", "known-plugin-2"]}
]
}
}
Add automated tests that verify schema accuracy against actual implementation to prevent drift between documentation and behavior.
When implementing networking APIs, always clearly document socket modes, configuration options, and platform-specific behaviors. Be explicit about blocking versus non-blocking mode implications, required flags, and ensure consistent API design across related components.
For socket modes, provide explicit warnings about incorrect configurations:
/// Create a new listener from a standard library socket.
///
/// Warning: Passing a listener in blocking mode is erroneous, and the
/// behavior in that case may change in the future. For example, it could panic.
pub fn from_std(socket: std::net::TcpListener) -> io::Result<TcpListener> {
// Implementation...
}
When exposing low-level socket options, document flag requirements and constraints:
/// Create a new AsyncFd with the provided raw epoll flags for registration.
///
/// These flags replace any epoll flags would normally set when registering the fd.
/// Note that `EPOLLONESHOT` must not be used, and `EPOLLET` must be set.
///
/// **Note**: This is an [unstable API][unstable]. The public API of this may
/// break in 1.x releases.
#[cfg(all(target_os = "linux", tokio_unstable))]
pub fn with_epoll_flags(inner: T, flags: u32) -> io::Result<Self>
For platform-specific features, include appropriate conditional compilation and clear handling:
// Properly handle platform-specific socket behaviors
#[cfg(any(target_os = "linux", target_os = "android"))]
let addr = {
let os_str_bytes = path.as_ref().as_os_str().as_bytes();
if os_str_bytes.starts_with(b"\0") {
StdSocketAddr::from_abstract_name(os_str_bytes)?
} else {
StdSocketAddr::from_pathname(path)?
}
};
Maintain consistent APIs when adding socket configuration methods, carefully handling type conversions when needed:
/// Sets the size of the UDP send buffer on this socket.
///
/// On most operating systems, this sets the `SO_SNDBUF` socket option.
pub fn set_send_buffer_size(&self, size: u32) -> io::Result<()> {
self.to_socket().set_send_buffer_size(size as usize)
}
Ensure tests cover both happy path scenarios and edge cases, including error conditions and boundary behaviors. Always wrap test code in proper test functions to leverage framework benefits like sanitizers and proper test isolation.
When writing tests, consider potential failure modes and edge cases that could cause crashes or unexpected behavior. For iterative operations, test what happens on subsequent iterations. For resource-intensive operations, ensure proper cleanup and error handling.
Example:
Deno.test("[node/sqlite] StatementSync#iterate edge cases", () => {
const db = new DatabaseSync(":memory:");
const stmt = db.prepare("SELECT 1 UNION ALL SELECT 2");
// Test normal iteration
const result1 = [];
for (const row of stmt.iterate()) {
result1.push(row);
}
// Test second iteration (edge case)
const result2 = [];
for (const row of stmt.iterate()) {
result2.push(row);
}
assertEquals(result1, result2); // Verify consistent behavior
});
Always prefer Deno.test() wrappers over standalone code to benefit from sanitizers, proper error handling, and test isolation.
When handling network buffers in protocol implementations, ensure proper lifecycle management to prevent memory leaks and data corruption. Key practices:
// RIGHT String eventStr = String.valueOf(evt); ctx.fireUserEventTriggered(evt); event.userEvent = eventStr;
2. Use buffer slicing and reference counting for partial data:
```java
// WRONG
byte[] partialData = new byte[5];
System.arraycopy(data, 0, partialData, 0, 5);
// RIGHT
ByteBuf chunk = ctx.alloc().buffer().writeBytes(data);
ByteBuf partial = chunk.retainedSlice(0, 5);
// ... use partial ...
partial.release();
chunk.release();
// After buffer submission
iovArray.clear(); // Clear references that are no longer needed
This guidance is especially important in asynchronous networking code where buffers may be accessed across different threads or event loop iterations.
When implementing algorithms with nested loops or recursive operations, carefully analyze the computational complexity and optimize the critical path. Pay attention to loop ordering, early termination conditions, and unnecessary recomputation.
In path normalization algorithms (discussion 111-112), avoid quadratic behavior by using tight single-pass loops instead of repeated string operations:
// Before: Potentially quadratic with repeated string operations
function getNormalizedAbsolutePath(path: string) {
// Repeatedly finds separators with indexOf
const sepIndex = path.indexOf(directorySeparator, index + 1);
const altSepIndex = path.indexOf(altDirectorySeparator, index + 1);
// ...
}
// After: Linear complexity with a single pass
function getNormalizedAbsolutePath(path: string) {
let index = 0;
while (index < path.length) {
const ch = path.charCodeAt(index);
// Process each character once with early termination
if (isAnyDirectorySeparator(ch)) {
// Handle separator
}
index++;
}
}
When working with array comparisons (discussions 110, 144), consider the computational complexity of your algorithm:
For recursive algorithms, apply similar principles to prevent stack overflows and excessive computation.
Always validate performance assumptions with concrete benchmarks rather than making optimization decisions based on intuition. When considering performance trade-offs between different implementation approaches, write targeted benchmarks to measure the actual impact.
For example, when debating between using arrays vs multiple function arguments, benchmark both approaches:
// Don't assume - measure the difference
// Option 1: Array approach
return respond(id, [{}, null]);
// Option 2: Multiple arguments
return respond(id, {}, null);
The benchmark revealed that “adding another arg is about 15% slower than the array version”, leading to a data-driven decision to keep the array approach.
Similarly, when choosing between async and sync operations, measure the performance impact in your specific context rather than following general patterns. What works for browsers may not be optimal for your runtime environment.
This approach prevents premature optimization, ensures changes actually improve performance, and helps avoid performance regressions disguised as improvements.
Each public API documentation block should follow a consistent structure:
String])Example:
/// Receives the next value from the channel.
///
/// This method returns `None` if the channel has been closed and there are
/// no remaining messages in the channel's queue. The channel is closed when
/// all senders have been dropped.
///
/// # Examples
///
/// ```
/// use tokio::sync::mpsc;
///
/// #[tokio::main]
/// async fn main() {
/// let (tx, mut rx) = mpsc::channel(100);
/// assert!(rx.recv().await.is_none());
/// }
/// ```
pub async fn recv(&mut self) -> Option<T> {
// Implementation
}
This structure improves readability and ensures documentation is both scannable and detailed when needed. The single-line summary is particularly important as it appears in module documentation and IDE tooltips.
Use descriptive names that clearly indicate the expected format, convention, or constraints. This applies to template variables, configuration keys, and validation patterns. Names should communicate not just what something is, but how it should be formatted or what rules it follows.
For template variables, include format hints in the name:
{
"description": "CAMEL_PARSER_NAME grammar for tree-sitter"
}
For validation patterns, ensure they accommodate all valid naming conventions:
{
"pattern": "^(source|text)(\\.[\\w\\-]+)+$"
}
This approach reduces ambiguity and helps developers understand naming requirements without consulting additional documentation.
Consistently use utility methods like ObjectUtil.checkNotNull() or Objects.requireNonNull() to validate that parameters are not null. When assigning parameters to instance variables, combine the null check with the assignment for cleaner, more maintainable code:
// Instead of:
public DohRecordEncoder(InetSocketAddress dohServer, boolean useHttpPost, String uri) {
if (dohServer == null) {
throw new NullPointerException("dohServer");
}
this.dohServer = dohServer;
this.useHttpPost = useHttpPost;
if (uri == null) {
throw new NullPointerException("uri");
}
this.uri = uri;
}
// Do this:
public DohRecordEncoder(InetSocketAddress dohServer, boolean useHttpPost, String uri) {
this.dohServer = ObjectUtil.checkNotNull(dohServer, "dohServer");
this.useHttpPost = useHttpPost;
this.uri = ObjectUtil.checkNotNull(uri, "uri");
}
This approach not only prevents NullPointerExceptions but also provides clear error messages, making debugging easier. For method parameters that should never be null, perform the check at the beginning of the method. When initializing components in a class, add appropriate null checks to the initialization or configuration methods rather than adding conditional null checks throughout the code.
When designing networking APIs, maintain consistency with existing interfaces while ensuring proper cross-platform compatibility. Follow these key principles:
u32 for buffer sizes), maintain this pattern even if underlying libraries use different types.// Good - matches existing TcpSocket API pattern
pub fn set_send_buffer_size(&self, size: u32) -> io::Result<()> {
self.to_socket().set_send_buffer_size(size as usize)
}
#[cfg(any(target_os = "linux", target_os = "android"))]
let addr = {
let os_str_bytes = path.as_ref().as_os_str().as_bytes();
if os_str_bytes.starts_with(b"\0") {
StdSocketAddr::from_abstract_name(os_str_bytes)?
} else {
StdSocketAddr::from_pathname(path)?
}
};
Abstraction boundaries: Consider whether low-level system calls should be exposed directly or via higher-level abstractions. When bypassing abstractions (like Mio), document the implications and ensure future compatibility.
Document constraints: When exposing low-level features like epoll flags, clearly document requirements and add debug assertions.
/// Create a new AsyncFd with the provided raw epoll flags for registration.
///
/// These flags replace any epoll flags would normally set when registering the fd.
/// Note that `EPOLLONESHOT` must not be used, and `EPOLLET` must be set.
// Good - direct access ensures correct result even during I/O operations
pub fn is_terminal(&self) -> bool {
std::io::stderr().is_terminal()
}
By following these principles, you’ll create networking APIs that are consistent, reliable across platforms, and easier for users to understand and use correctly.
Always ensure resources are properly released, especially in exception paths. Use try-finally blocks to guarantee cleanup of buffers, streams, and other resources even when errors occur.
Common mistakes include:
Example of problematic code:
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf content = ctx.alloc().buffer();
dohQueryEncoder.encode(ctx, (DnsQuery) msg, content);
HttpRequest request = createRequest(content);
// If an exception occurs here, both content and msg will leak
super.write(ctx, request, promise);
}
Better implementation:
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
DnsQuery query = (DnsQuery) msg;
ByteBuf content = ctx.alloc().buffer();
try {
dohQueryEncoder.encode(ctx, query, content);
HttpRequest request = createRequest(content);
ctx.write(request, promise);
} finally {
content.release();
query.release();
}
}
Also ensure proper cleanup in lifecycle methods like handlerRemoved() and doClose() to prevent leaks, and when throwing exceptions, release any allocated resources first:
if (!IoUring.isRegisterBufferRingSupported()) {
// Close ringBuffer before throwing to ensure we release all memory on failure
ringBuffer.close();
throw new UnsupportedOperationException("io_uring_register_buffer_ring is not supported");
}
When handling error conditions, prefer specific exceptions with clear messages:
// Bad
if (encoderFactory == null) {
throw new Error();
}
// Good
if (encoderFactory == null) {
throw new IllegalStateException("Couldn't find CompressionEncoderFactory: " + targetContentEncoding);
}
Prefer runtime-configurable values over hardcoded constants to allow users to customize behavior without recompilation. Use environment variables with os.getenv_opt() for runtime configuration instead of compile-time constants with $d(), and consider environment variables instead of complex CLI parsing.
Why this matters:
How to apply:
// Instead of hardcoded constants:
const indexexpr_cutoff = 10
// Use runtime-configurable defaults:
const indexexpr_cutoff = os.getenv_opt('VET_INDEXEXPR_CUTOFF') or { '10' }.int()
// For buffer sizes and similar values:
const buff_size = int($d('gg_text_buff_size', 2048))
// Instead of complex CLI parsing for tools:
// Use environment variables like VDIFF_TOOL, VTMP_DIR, etc.
This approach allows users to customize behavior with simple commands like VET_INDEXEXPR_CUTOFF=99 v vet . without requiring recompilation or complex flag combinations.
When using kernel-specific networking features like io_uring, always implement runtime detection of feature support before enabling them. This ensures your code remains compatible across different kernel versions while taking advantage of newer optimizations when available.
For Linux kernel features, verify the minimum required version and implement conditional feature activation. This prevents runtime errors (like EINVAL) on systems with older kernels that don’t support specific features.
Example implementation:
struct io_uring_params p;
memset(&p, 0, sizeof(p));
// Check for feature support before enabling
#ifdef IORING_SETUP_SUBMIT_ALL
// Introduced in kernel 5.18, don't blindly enable
if (kernel_supports_feature(FEATURE_IORING_SETUP_SUBMIT_ALL)) {
p.flags |= IORING_SETUP_SUBMIT_ALL;
}
#endif
#ifdef IORING_SETUP_R_DISABLED
// Introduced in kernel 6.1, check before using
if (kernel_supports_feature(FEATURE_IORING_SETUP_R_DISABLED)) {
p.flags |= IORING_SETUP_R_DISABLED;
}
#endif
Documentation should clearly state which kernel versions are required for specific features. Consider exposing feature detection capabilities to higher-level code so callers can make informed decisions about available functionality.
Ensure that all API documentation, including code examples and integration guides, accurately reflects the current API state. When API changes occur (such as import/export modifications, new entry points, or version compatibility updates), corresponding documentation must be updated immediately to prevent developer confusion and integration errors.
Key areas to verify:
Example of outdated documentation that needs correction:
// Outdated - no longer works
import Parser from 'web-tree-sitter';
// Current - matches actual API
import { Parser } from 'web-tree-sitter';
// Also document available variants
import { Parser } from 'web-tree-sitter/debug';
This practice prevents developers from encountering runtime errors due to mismatched documentation and ensures smooth API adoption.
Names should clearly convey their semantic purpose and avoid conflicts that reduce code readability. This applies to imports, types, variables, and methods.
Key principles:
Examples:
Import naming to avoid conflicts:
// Problematic - naming conflict
import Parser from 'web-tree-sitter';
const Parser: typeof Parser = await import('..').then(m => m.default);
// Better - clear, distinct names
import TSParser from 'web-tree-sitter';
const Parser: typeof TSParser = await import('..').then(m => m.default);
Type naming for semantic clarity:
// Problematic - same interface for different concepts
interface QueryResult {
pattern: number;
captures: { name: string; node: SyntaxNode }[];
}
// Better - distinct names for different concepts
interface QueryMatch {
pattern: number;
captures: QueryCapture[];
}
interface QueryCapture {
name: string;
node: SyntaxNode;
}
This prevents confusion and makes the codebase more maintainable by ensuring each name has a clear, unambiguous meaning.
Always use appropriate preprocessor directives to guard platform-specific, version-dependent, or feature-specific code. Ensure that conditional compilation checks accurately reflect the actual availability of features and APIs, not just version numbers or single configuration flags.
When gating functionality:
Example of proper conditional compilation:
#ifdef TREE_SITTER_FEATURE_WASM
// WASM-specific code that requires __builtin_wasm_memory_size
#endif
#if PY_MINOR_VERSION >= 13 && !defined(Py_GIL_DISABLED)
{Py_mod_gil, Py_MOD_GIL_NOT_USED},
#endif
#elif !defined(__wasi__)
// Platform-specific implementation
#else
// WASI fallback or noop implementation
#endif
This prevents compilation errors across different environments and ensures code portability while maintaining feature availability where supported.
When modifying existing public APIs, always maintain backward compatibility to avoid breaking existing users. Breaking changes include altering function signatures, removing public functions, or changing function visibility from public to private.
Instead of making breaking changes:
For example, when refactoring a public function:
// Instead of changing the signature (breaking change):
// pub fn new_digest(absorption_rate int, hash_size int, suffix u8) !&Digest
// Keep the original public function:
pub fn new_digest(absorption_rate int, hash_size int) !&Digest {
return new_digest_with_suffix(absorption_rate, hash_size, default_suffix)
}
// Add the new functionality as a separate function:
pub fn new_digest_with_suffix(absorption_rate int, hash_size int, suffix u8) !&Digest {
// implementation
}
When removing public functions, keep them as wrappers that call the new internal implementation. This ensures existing code continues to work while allowing internal refactoring and improvements.
Write cleaner and more maintainable CI/CD workflows by following established syntax patterns and best practices. Use GitHub Actions’ built-in features like working-directory instead of manual directory changes, employ proper shell quoting and efficient commands, and prefer single-line commands when appropriate.
Key practices:
working-directory parameter instead of cd commands in run steps"$variable") and use efficient command alternatives (tar -xz -C instead of --directory=)mkdir -p to handle existing directories gracefullyExample improvements:
# Instead of:
- name: Build WASM Library
run: |
cd lib/binding_web
npm ci
npm run build:debug
# Use:
- name: Build WASM Library
working-directory: lib/binding_web
run: npm ci && npm run build:debug
# Instead of:
- run: |
echo "DOCKER_CMD=docker run --rm -v /home/runner:/home/runner -w . $CROSS_IMAGE" >> $GITHUB_ENV
# Use:
- run: echo "DOCKER_CMD=docker run --rm -v /home/runner:/home/runner -w . $CROSS_IMAGE" >> $GITHUB_ENV
These optimizations improve readability, reduce potential errors, and make workflows more maintainable while following GitHub Actions conventions.
Use clear, consistent terminology in all error messages and user-facing text. Avoid technical jargon that might confuse users, and maintain consistency in language patterns across the codebase:
// Preferred
"Default imports are not allowed in a deferred import."
// Avoid
"Default imports aren't allowed for deferred imports."
Consistent terminology improves readability and maintains a professional style throughout the codebase.
Security-critical functions, especially those involving cryptographic operations, must include clear and accurate documentation about their security requirements and assumptions. This is essential for preventing misuse that could lead to vulnerabilities.
When documenting cryptographic functions:
Example from ECDSA key generation:
// new_key_from_seed creates a new private key from the seed bytes.
//
// Notes on the seed:
// You should make sure the seed bytes come from a cryptographically secure random generator,
Poor documentation of cryptographic requirements can lead developers to make incorrect assumptions about security properties, potentially introducing vulnerabilities through improper usage of otherwise secure cryptographic primitives.
Always explicitly specify security-critical parameters in cryptographic operations rather than relying on default values. This prevents vulnerabilities that could arise from misunderstood defaults or future changes to default behavior.
When working with cryptographic APIs, be explicit about hash algorithms, key sizes, padding schemes, and other security-relevant configurations. This makes the security intent clear and protects against breaking changes to library defaults.
Example of what to avoid:
// Unclear what hash algorithm is being used
signature := pvkey.sign(message_tobe_signed)!
Example of preferred approach:
// Explicitly specify the hash configuration
signature := pvkey.sign(message_tobe_signed, hash_config: .with_recommended_hash)!
This practice is especially important when there’s uncertainty about what the default behavior actually is, as defaults in security libraries can change between versions or may not be what developers expect.
-prod for performanceAlways use the -prod compiler flag when building performance-critical code or running benchmarks. The -prod flag automatically enables essential optimization settings including -O3 and -flto (Link Time Optimization), eliminating the need to manually specify these common performance flags.
This approach ensures consistent optimization across different build scenarios and reduces the likelihood of forgetting important performance flags. For additional architecture-specific optimizations, you can combine -prod with targeted flags:
# For performance-critical applications
v -cc gcc -prod -cflags "-std=c17 -march=native -mtune=native" .
# For benchmarking
v -prod run bench/bench.v
Using -prod as the foundation provides a reliable baseline for performance optimization while allowing for additional customization when needed.
Group related code elements together with a consistent and logical structure. Place stable fields and functionality first, followed by conditional or unstable features.
For struct definitions:
#[derive(Debug, Default)]
pub(crate) struct WorkerMetrics {
// Stable fields first
busy_duration_total: AtomicU64,
// Conditional fields after stable ones
#[cfg(tokio_unstable)]
park_count: AtomicU64,
#[cfg(tokio_unstable)]
mean_poll_time: AtomicU64,
// ...more fields
}
For implementation blocks:
impl MetricsBatch {
// Regular methods first
pub(crate) fn submit(&mut self, worker: &WorkerMetrics) {
worker.busy_duration_total.store(self.busy_duration_total, Relaxed);
// Call separate method for unstable features
self.submit_unstable(worker);
}
// Unstable methods grouped together at the bottom
cfg_unstable_metrics! {
fn submit_unstable(&mut self, worker: &WorkerMetrics) {
// Implementation for unstable features
}
}
}
Keep implementation blocks manageable by splitting large code blocks into separate files, especially within macros where formatting tools don’t work well. Consider exposing single-item modules as direct exports:
// Instead of this:
pub mod abort_on_drop;
// Do this:
mod abort_on_drop;
pub use abort_on_drop::AbortOnDropHandle;
When public types would clutter documentation, organize them into logical submodules.
When writing code that handles potentially null, undefined, or uninitialized values, always document safety assumptions with clear comments. This is especially important for unsafe blocks that manipulate raw pointers, uninitialized memory, or perform type conversions.
Good safety comments should:
Example:
// BAD: Missing safety explanation
unsafe fn read_from<T: Read>(&mut self, rd: &mut T, max_buf_size: usize) -> io::Result<usize> {
let buf = &mut self.buf.spare_capacity_mut()[..max_buf_size];
let buf = unsafe { &mut *(buf as *mut [MaybeUninit<u8>] as *mut [u8]) };
let res = rd.read(buf);
// ...
}
// GOOD: Clear safety documentation
/// Safety: `rd` must not read from the buffer passed to `read` and
/// must correctly report the length of the written portion of the buffer.
unsafe fn read_from<T: Read>(&mut self, rd: &mut T, max_buf_size: usize) -> io::Result<usize> {
// SAFETY: The memory may be uninitialized, but `rd.read` will only write to the buffer.
let buf = &mut self.buf.spare_capacity_mut()[..max_buf_size];
let buf = unsafe { &mut *(buf as *mut [MaybeUninit<u8>] as *mut [u8]) };
let res = rd.read(buf);
// ...
}
When performing pointer casts or transmutes, be especially explicit about lifetime and ownership guarantees:
// GOOD: Clear pointer casting documentation
let inner: *mut InnerFuture<'static, T> = &mut self.0;
let inner: *mut InnerFuture<'_, T> = inner.cast();
// SAFETY: The future must not exist after the type `T` becomes invalid.
// This casts away the type-level lifetime check, but the inner future
// never moves out of this structure, so it won't live longer than `T`.
let inner = unsafe { &mut *inner };
Proper documentation of safety assumptions helps prevent subtle bugs, assists future maintainers, and makes code auditing more effective.
When configuring build exclusions in package manifests or configuration files, be strategic and minimal. Only exclude files that cause actual build issues (like non-source files that generate compilation warnings), while preserving important files such as licenses and directories that may be relevant for future features. Modern tooling often handles file filtering automatically, so explicit exclusion lists may be unnecessary.
For example, in Package.swift:
.target(name: "TreeSitter",
path: "lib",
exclude: [
// Only exclude files that cause build warnings
"src/unicode/ICU_SHA",
"src/unicode/README.md",
"src/unicode/LICENSE",
// Don't exclude "src/wasm" - may be needed for future support
],
Before adding exclusions, verify they solve actual build problems rather than preemptively excluding files that might seem irrelevant. Consider whether newer versions of your build tools can handle the filtering automatically.
Choose identifiers that clearly communicate their purpose, content, or role rather than using generic or abbreviated names. Names should be self-documenting and reduce the need to reference documentation or implementation details to understand what they represent.
Apply this principle to:
Examples of improvements:
// Before: Generic field name
.supertypes = ts_supertypes,
// After: Specific field name indicating content type
.supertype_symbols = ts_supertype_symbols,
// Before: Vague error name
ReservedWordSet,
// After: Descriptive error name with context
InvalidReservedWordSet,
// Before: Generic lifetime parameter
QueryMatches<'a, 'tree: 'a, 'b: 'a, T: TextProvider<'b>>
// After: Descriptive lifetime parameter
QueryMatches<'a, 'tree: 'a, 'text_provider: 'a, T: TextProvider<'text_provider>>
// Before: Ambiguous field name
pub expected: String,
// After: Clear field name indicating purpose
pub expected_capture_name: String,
This approach improves code readability and reduces cognitive load for developers who need to understand the codebase quickly.
Always provide meaningful, specific error messages that help users understand what went wrong and why. Use descriptive language that indicates what was expected versus what was encountered. Leverage error context tools like with_context() to add relevant information about the operation that failed, and use debug formatting for error display to show complete error chains.
Key practices:
with_context() to explain what operation was being performed{:?}) when printing errors to display the full error chain with “Caused by:” informationExample:
// Instead of generic error
return Err(anyhow!("Invalid rule"));
// Provide specific context
return Err(ParseGrammarError::UnexpectedRule)?;
// Add operation context
fs::read_to_string(&location)
.with_context(|| format!("failed to read {}", location.to_string_lossy()))?;
// Use debug format for complete error information
eprintln!("{:?}", err);
This approach makes debugging significantly easier by providing actionable information about what failed and why, rather than forcing developers to guess or dig through code to understand error conditions.
Structure your CI workflows to maximize performance and clarity. Each job should have a single, clear responsibility with these guidelines:
One toolchain per job: Keep jobs focused with a single Rust toolchain version per job. Multiple toolchains in one job create confusion about which version is being used for each command.
# Don't do this:
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_stable }}
- name: Install Rust nightly
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.rust_nightly }}
Instead, create separate jobs for different toolchains or requirements.
Split long-running tasks: Divide lengthy operations into separate jobs that can run in parallel. When a job begins taking too much time (like Miri tests), split it to enable faster feedback cycles and better resource utilization.
Use efficient testing tools: Employ optimized testing tools like cargo-nextest to significantly reduce execution time. For example, replacing standard test runners with nextest can reduce CI runtime by 40% or more, as seen with Miri jobs (37 minutes → 21 minutes).
These practices will result in more maintainable CI configurations, faster feedback for developers, and clearer error identification when builds fail.
Structure CI jobs for clarity, parallelism, and efficiency. Each job should have a single, well-defined purpose to prevent confusion about which tools and versions are being used.
Key guidelines:
For example, instead of:
jobs:
test-all:
steps:
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
- name: Run multiple test suites
run: |
cargo test
cargo miri test
Prefer:
jobs:
test-stable:
steps:
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Run tests
run: cargo test
test-miri:
steps:
- uses: dtolnay/rust-toolchain@stable
with:
toolchain: nightly
components: miri
- uses: taiki-e/install-action@v2
with:
tool: cargo-nextest
- name: Run miri tests
run: cargo miri nextest run
This approach avoids confusion about which toolchain is being used for each command and allows jobs to run in parallel, significantly improving pipeline efficiency. Tests that were taking 37 minutes can be reduced to 21 minutes by using optimized tools like cargo-nextest.
When implementing algorithms, make optimization decisions based on the specific characteristics of your data types and usage patterns rather than using generic approaches. Consider factors like type semantics, memory layout, and processing requirements to choose the most appropriate algorithmic strategy.
For type handling, select operations that preserve type semantics - for example, when processing int vs uint64, use type-specific operations to avoid incorrect processing:
func OP_int() Op {
switch _INT_SIZE {
case 32:
return OP_i32
case 64:
return OP_i64
default:
panic("unsupported int size")
}
}
// Add CompatOp to distinguish int from uint64 processing
p.Add(ir.OP_int(), ir.OP_i)
For memory management, tailor initialization strategies based on data characteristics - scan vs noscan types may require different clearing approaches:
// Clear memory for noscan types when makeslice semantics require initialization
if et.PtrData == 0 {
// Clear [oldLen:newLen] to avoid dirty data for skipped types
}
For compilation algorithms, optimize based on code complexity - simple operations like pointer dereferencing can be fully inlined rather than using generic recursion:
// Inline simple pointer operations instead of increasing recursion depth
for et.Kind() == reflect.Ptr {
// Handle pointer-specific interfaces before dereferencing
if pt.Implements(jsonUnmarshalerType) {
// Handle directly without recursion
return
}
et = pt.Elem()
}
This approach improves both performance and correctness by matching algorithmic choices to the specific requirements and constraints of your data.
When designing new API functions, maintain consistent patterns across the codebase and avoid creating precedents that lead to function proliferation or inconsistent interfaces. Consider how the design will scale to similar use cases and whether it aligns with existing API conventions.
For example, instead of adding separate count functions for each data type:
// Avoid this pattern - leads to function proliferation
uint32_t ts_language_supertype_count(const TSLanguage *self);
uint32_t ts_language_field_count(const TSLanguage *self);
uint32_t ts_language_symbol_count(const TSLanguage *self);
// Prefer this pattern - consistent with existing APIs
const TSSymbol *ts_language_supertypes(const TSLanguage *self, uint32_t *length);
Similarly, when deciding between separate namespaces versus unified approaches, consider the actual usage patterns and whether the separation provides meaningful benefits to API consumers. Balance API simplicity with practical usage needs, and document the reasoning behind design decisions to maintain consistency in future additions.
When making design decisions that could impact performance, evaluate resource implications and provide concrete measurements rather than assumptions. Consider memory usage, binary size, and allocation patterns in your choices.
For API design, prefer memory-efficient data structures when they provide equivalent functionality. For example, use symbol IDs instead of strings when the IDs serve the same purpose but consume less memory and reduce binary size.
When concerned about performance costs, benchmark the actual impact:
// Instead of assuming new/delete is slow, measure it
$ for x in $(echo "1 2 3 4 5"); do time ./out/Test/tests >/dev/null; done
real 0m19.423s // Before optimization
real 0m20.394s // After change - minimal impact
Always validate performance assumptions with data, especially for frequently called code paths or resource-constrained environments like WebAssembly.
When designing APIs that handle numeric values, ensure comprehensive support for different integer types (Int64, Uint64) based on range requirements and use case needs. Consider practical constraints such as migration costs and legacy system compatibility when deciding between unified or separate configuration options.
For configuration APIs, provide separate options when legacy compatibility is crucial:
type Config struct {
// Uint64 into strings on Marshal
Uint64ToString bool
// Int64 into strings on Marshal (separate option for migration compatibility)
Int64ToString bool
}
For data access APIs, provide complete numeric type coverage:
// Int64 casts the node to int64 value
func (self *Node) Int64() (int64, error) { ... }
// Uint64 casts the node to uint64 value (needed for full range support)
func (self *Node) Uint64() (uint64, error) { ... }
This approach ensures APIs can handle the full spectrum of numeric requirements while accommodating real-world migration and compatibility constraints.
Avoid using real sleeps or delays in tests as they significantly slow down the test suite and can introduce flakiness. Instead, use simulated time with the start_paused parameter for time-dependent tests:
// Instead of this:
#[tokio::test]
async fn slow_test() {
// This will actually wait 500ms, making tests slow
tokio::time::sleep(Duration::from_millis(500)).await;
// test logic...
}
// Do this:
#[tokio::test(start_paused = true)]
async fn fast_test() {
// This will execute immediately with simulated time
tokio::time::sleep(Duration::from_millis(500)).await;
// test logic...
}
For tests that need to respond to events or could potentially hang (like waiting for signals), consider:
With hundreds of tests in the codebase, even small performance improvements per test can significantly reduce the overall test execution time.
Always verify that configuration files completely and accurately specify their requirements. This includes ensuring all related files are included and using appropriate minimum versions or glob patterns.
For package configurations, use glob patterns to include all related files rather than listing them individually:
"files": [
"dist/binaries/**",
"dist/src/**",
"src/**",
"load.*"
]
For framework configurations, specify the minimum required version that supports your needs:
"frameworks": {
"netstandard1.1": {}
}
Before finalizing configuration changes, double-check that all necessary artifacts, dependencies, and version requirements are properly captured to avoid missing components or compatibility issues.
Ensure algorithms produce expected results by validating data flow and checking that all execution paths return meaningful values rather than undefined or incorrect results. Pay special attention to conditional logic that filters or processes data, and verify that functions receive the necessary arguments to perform their intended operations.
For example, when implementing filtering logic, ensure all condition branches are properly handled:
// Verify complex conditional logic covers all cases
((typeof listener.options === "boolean" &&
listener.options === options.capture) ||
(typeof listener.options === "object" &&
listener.options.capture === options.capture)) &&
listener.callback === callback
Also validate that functions receive required data to avoid undefined results:
// Ensure functions get necessary arguments for proper execution
function sniffImage(input) {
// Pass required Uint8Array to algorithm
return imageTypePatternMatchingAlgorithm(input);
}
Before merging, trace through algorithm execution paths to confirm they produce the intended outputs and handle edge cases appropriately.
Ensure technical documentation uses clear, grammatically correct language with consistent formatting when explaining naming conventions and code structure. This includes proper capitalization, precise word choice, consistent code formatting with backticks, and clear sentence structure.
Key practices:
module names)Example of good technical writing:
Modules names in .v files must match the name of their directory.
A .v file `./abc/source.v` must start with `module abc`. All .v files in this directory belong to the same module `abc`. They should also start with `module abc`.
Clear technical writing helps developers understand naming conventions and reduces confusion when implementing code structure requirements.
When documenting technical features, APIs, or data structures, provide comprehensive coverage that includes related functionality and detailed explanations rather than minimal inline comments.
For complex technical concepts, prefer detailed prose explanations that can accommodate full context and examples. When documenting one feature, ensure related features are also covered to provide complete understanding.
For example, instead of:
typedef struct {
uint32_t row; // zero-based
uint32_t column; // zero-based, measured in bytes
Prefer comprehensive prose documentation:
In a point, rows and columns are zero-based. The `row` field represents the number of newlines before a given position, while `column` represents the number of bytes between the position and beginning of the line.
Similarly, when documenting predicates like #any-eq?, also document related predicates like #any-not-eq? to provide complete coverage of the feature set. This approach ensures developers have all necessary information in one place and reduces the need to search for related functionality elsewhere.
Always validate and securely load external resources like libraries, configuration files, and modules to prevent tampering and hijacking attacks. When implementing security features that depend on system components:
Example:
// Secure approach - use absolute paths and verify library existence
HMODULE security_module = LoadLibraryExA(
"C:\\Windows\\System32\\wldp.dll", // Use absolute path
NULL,
LOAD_LIBRARY_SEARCH_SYSTEM32); // Restrict search to system directory
// Check if module is available before using its functions
if (security_module != NULL) {
// Feature is supported, load function pointers
pfnSecurityFunction = GetProcAddress(security_module, "SecurityFunction");
if (pfnSecurityFunction != NULL) {
// Use security feature
} else {
// Handle missing function with appropriate fallback
}
} else {
// Module not available, implement secure fallback behavior
}
This approach helps prevent attackers from exploiting search path vulnerabilities to load malicious libraries or resources, which could lead to code execution or privilege escalation attacks.
When functions contain complex inline logic or duplicated code, extract this logic into separate functions or shared utilities to improve readability and maintainability. Complex inline code makes functions harder to understand and test, while duplication creates synchronization issues and increases the likelihood of bugs.
For complex inline logic, create dedicated functions:
// Instead of complex inline logic:
function mapToCallback(context, callback, onError) {
return async function (req) {
const asyncContext = getAsyncContext();
setAsyncContext(context.asyncContext);
try {
// ... complex logic here ...
} finally {
// ... cleanup logic ...
}
};
}
// Extract into a separate function:
function mapToCallback(context, callback, onError) {
return async function (req) {
return handleRequestWithContext(req, context, callback, onError);
};
}
For duplicated code between similar implementations (like sync/async versions), consider:
This approach reduces cognitive load, makes code easier to test, and prevents bugs that arise from maintaining multiple copies of similar logic.
API documentation should provide clear, unambiguous explanations of function behavior, return values, and edge cases. Avoid vague or obscure wording that leaves developers guessing about implementation details.
For return values, explicitly state the conditions that lead to each possible return value rather than using generic phrases. For complex or non-obvious functionality, include practical examples that demonstrate real-world usage scenarios.
Example of unclear documentation:
/**
* Set the range of bytes or (row, column) positions in which the query
* will be executed.
*
* If the provided range was set, return `true`. Otherwise, return `false`.
*/
Improved version:
/**
* Set the range of bytes or (row, column) positions in which the query
* will be executed.
*
* This will return false if the start byte/point is greater than the end
* byte/point, otherwise it will return true.
*/
For obscure functions, add examples showing practical use cases:
/**
* Get a child of 'from_parent' which is a parent to the given node.
* The parent may be indirect.
*
* Example: This can be used to efficiently find a path from a root node
* to traverse up the syntax tree within a specific subtree.
*/
Clear documentation benefits both immediate development and automated tooling like language bindings generation.
Functions should validate input parameters at the beginning and return appropriate error indicators (like false, null, or error codes) rather than proceeding with invalid data. This prevents downstream errors and provides clear feedback to callers about invalid usage.
Additionally, ensure error states are properly scoped to avoid misleading results when using shared resources or global state. Error status should accurately reflect the state of the specific operation or object being queried.
Example of early input validation with error return:
bool ts_query_cursor_set_byte_range(
TSQueryCursor *self,
uint32_t start_byte,
uint32_t end_byte
) {
if (end_byte == 0) {
end_byte = UINT32_MAX;
} else if (start_byte > end_byte) {
return false; // Early validation with clear error signal
}
// ... proceed with valid inputs
}
This approach makes error conditions explicit and prevents functions from operating on invalid data, leading to more predictable and debuggable code.
Always validate boundary conditions and termination criteria in algorithms to prevent infinite loops, out-of-bounds access, and incorrect behavior. This includes checking loop bounds against variable type limits, validating range parameters, and ensuring proper termination conditions.
Key practices:
Example from loop bounds checking:
// Problem: infinite loop if count > UINT16_MAX
for (TSSymbol i = 0; i < count; i++) { ... }
// Solution: add explicit bounds check
for (TSSymbol i = 0; i < count && i < UINT16_MAX; i++) { ... }
Example from range validation:
// Problem: incorrect logic for range exclusion
if (end_byte <= start_byte && point_lte(end_point, start_point)) { ... }
// Solution: use OR for exclusion checks
bool node_precedes_range = (
ts_node_end_byte(node) <= self->start_byte ||
point_lte(ts_node_end_point(node), self->start_point)
);
Proper boundary validation prevents algorithmic failures and improves both correctness and performance by avoiding unnecessary computation on invalid inputs.
Maintain code readability and organization by extracting focused, well-named functions and using appropriate scoping. Break down complex logic into smaller, focused functions and utilize extension functions when appropriate to improve code clarity.
Key guidelines:
Example - Before:
fun isValidFloat(s: String): Boolean {
var start = 0
var end = s.length - 1
while (start <= end && s[start].code <= 0x20) start++
if (start > end) return false
while (end > start && s[end].code <= 0x20) end--
if (s[start] == '+' || s[start] == '-') start++
if (start > end) return false
// ... more complex logic
}
After:
fun isValidFloat(s: String): Boolean {
val (start, end) = s.findContentBounds() ?: return false
val (newStart, hasSign) = s.parseSign(start)
if (newStart > end) return false
// ... more focused logic
}
private fun String.findContentBounds(): Pair<Int, Int>? {
var start = 0
var end = length - 1
while (start <= end && this[start].code <= 0x20) start++
if (start > end) return null
while (end > start && this[end].code <= 0x20) end--
return start to end
}
private fun String.parseSign(start: Int): Pair<Int, Boolean> {
return when (this[start]) {
'+', '-' -> (start + 1) to true
else -> start to false
}
}
When configuring TypeScript projects, establish a clear understanding of module and type resolution hierarchies, especially when using different package managers like npm or Yarn’s Plug’n’Play (PnP).
For module resolution configuration:
// For PnP environments, use direct resolution instead of fallbacks
const searchResult = isPnpAvailable()
? tryLoadModuleUsingPnpResolution(Extensions.DtsOnly, typeName, location, moduleState)
: loadModuleFromNearestNodeModulesDirectory(Extensions.DtsOnly, typeName, location, moduleState);
Maintain a consistent resolution priority hierarchy: paths > baseUrl > typeRoots. This order respects specificity, with exact matches taking precedence over general directory searches.
vpath.validate(path, vpath.ValidationFlags.Absolute | vpath.ValidationFlags.AllowWildcard);
These practices help avoid resolution conflicts and ensure consistent behavior across different development environments and package manager configurations.
Prioritize memory-efficient data structures and algorithms by favoring stack allocation over heap allocation, utilizing bit packing for compact storage, and applying proper bit masking for data integrity. When designing iterators or containers, consider whether operations can avoid dynamic allocation - most backtracking scenarios use empty vectors that don’t require heap allocation. For packed data structures, use bit masking to ensure only valid bits are stored and prevent data corruption.
Example of proper bit masking:
// Instead of direct assignment
flags_.direction = static_cast<uint8_t>(direction);
// Use masking to ensure only valid bits
flags_.direction = static_cast<uint8_t>(direction) & 0x03;
For memory layout optimization, combine related fields to utilize alignment padding:
// Before: wastes alignment bits
bool flag1 : 1;
bool flag2 : 1;
uint32_t generation = 0; // 32 bits but alignment wastes space
// After: pack into available bits
bool flag1 : 1;
bool flag2 : 1;
uint8_t generation = 0; // Fits in remaining alignment space
Always validate that algorithmic optimizations maintain correctness - performance improvements should not compromise the fundamental behavior of the algorithm.
Implement consistent algorithms for type compatibility and comparison across different data structures. When developing type checking logic, ensure similar operations (like assignment compatibility, intersection, or mapping) behave predictably regardless of the underlying type structure.
For example, when handling tuple types versus object types:
// Ensure consistent behavior between these patterns
// Rest parameter tuple compatibility
type RestTuple = (x: string, ...args: [string] | [number, boolean]) => void;
const handler = (a: string, b: string | number, c?: boolean) => {};
// Should be assignable with consistent rules
// Tuple intersection handling
type A = [number, number] & [string, string];
// Should be handled as position-wise intersection: [number & string, number & string]
// Object vs tuple mapped type behavior
type MappedObj<T> = { [K in keyof T]: T[K] };
type MappedTuple<T extends any[]> = { [K in keyof T]: T[K] };
// Should follow consistent inference patterns
Pay special attention to:
This consistency leads to more predictable type system behavior and fewer edge cases for developers to navigate.
Configure test suites to run under multiple specialized environments to catch issues that might not appear in standard test runs. This includes testing with different compiler configurations, using specialized testing tools, and properly preserving environment variables when adding test-specific flags.
Example:
# In CI workflow
- name: Test with panic=abort
run: |
RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo test --workspace --all-features
- name: Test with memory safety analyzer
run: |
cargo miri test --features full --lib --tests --no-fail-fast
env:
MIRIFLAGS: -Zmiri-disable-isolation -Zmiri-strict-provenance
When adding new test environments, ensure you don’t overwrite existing environment variables (append instead) and consider the maturity of experimental features when selecting testing configurations.
Include testing configurations that mirror production environments to catch issues that might only manifest in release builds. For example, testing with panic=abort can reveal issues that wouldn’t appear when testing with the default panic=unwind setting. Similarly, when using specialized testing tools like Miri, configure the appropriate flags to ensure comprehensive validation under realistic conditions.
Example:
- name: test all --all-features panic=abort
run: |
RUSTFLAGS="$RUSTFLAGS -C panic=abort -Zpanic-abort-tests" cargo nextest run --workspace --all-features --tests
When adding new test configurations, always preserve existing environment variables by appending to them rather than replacing them entirely, which ensures that other important configurations remain intact.
Use target-specific CMake commands instead of global ones to ensure proper scoping and avoid polluting the global build environment. This improves maintainability and prevents unintended side effects when building multiple targets.
Replace global configuration commands with their target-specific equivalents:
target_compile_definitions() instead of add_definitions()target_compile_options() instead of setting CMAKE_C_FLAGStarget_include_directories() instead of include_directories()Example:
# Avoid global settings
add_definitions(-D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE)
set(CMAKE_C_FLAGS "-O3 -Wall -Wextra -Wshadow")
# Prefer target-specific settings
target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE)
target_compile_options(tree-sitter PRIVATE -O3 -Wall -Wextra -Wshadow -Wno-unused-parameter -pedantic)
This approach ensures that configuration settings only apply to the intended targets and makes the build system more modular and predictable.
When implementing CMake build automation for code generation, carefully choose between add_custom_command and add_custom_target based on your execution requirements. Use add_custom_command with explicit OUTPUT and DEPENDS for files that should be automatically generated when missing or when dependencies change. Add add_custom_target only when you need manual invocation capability in addition to automatic dependency resolution.
Always specify clear OUTPUT, DEPENDS, WORKING_DIRECTORY, and COMMENT parameters to make the build process transparent and debuggable. Test that your custom commands work both automatically (when dependencies change) and can be validated independently.
Example:
# Automatic generation when src/parser.c is missing or grammar.json changes
add_custom_command(OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/parser.c"
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/grammar.json"
COMMAND "${TREE_SITTER_CLI}" generate src/grammar.json
--abi=${TREE_SITTER_ABI_VERSION}
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
COMMENT "Generating parser.c")
# Optional: Add target for manual invocation
add_custom_target(generate DEPENDS src/parser.c)
Document the purpose and usage of each custom target to avoid confusion about when automatic vs manual execution occurs.
Choose semantically appropriate identifiers and types that clearly represent their purpose. This includes using the correct data type (e.g., boolean for flags instead of integers) and ensuring proper spelling of all identifiers.
For boolean flags:
// Not recommended
int reboot_default = 0; // Using integer for a boolean flag
// Recommended
bool reboot_default = false; // Clear semantic meaning
For function and variable names:
Using semantically appropriate identifiers improves code readability, reduces bugs, and makes maintenance easier by clearly expressing intent.
When working with properties or methods that could potentially be null, use Kotlin’s null safety features defensively even if you believe the value should never be null. This prevents runtime exceptions when assumptions change or when interacting with Java/third-party code.
Instead of direct access that assumes non-null values:
// Risky: Assumes extensionReceiverParameter, type, and classFqName are all non-null
if (it.owner.extensionReceiverParameter.type.classFqName == receiver) {
// ...
}
Use safe call chains and provide fallback values:
// Safe: Handles potential nulls gracefully
return classOrNull?.let {
it.signature?.asPublic()?.let { sig ->
sig.packageFqName == packageName && sig.declarationFqName.matches(typeNameReg)
}
} ?: false
// Another example with safe call chains
if (it.owner.extensionReceiverParameter?.type?.classFqName == receiver) {
// ...
}
This approach is especially important when:
Remember that source locations, error reporting systems, and other infrastructure often assume non-null values. Adding defensive checks prevents crashes even when the underlying problem may be elsewhere.
When specifying file paths in configuration files (like ESLint or TypeScript configs), use absolute paths rather than relative paths to avoid path resolution issues. Tools may interpret relative paths differently based on their internal working directory, leading to unexpected behavior.
Example:
// Problematic - using relative paths
parserOptions: {
projectService: true,
defaultProject: "./scripts/tsconfig.json",
}
// Better - using absolute paths
parserOptions: {
tsconfigRootDir: __dirname,
projectService: {
defaultProject: path.join(__dirname, "scripts", "tsconfig.json"),
}
}
This practice prevents issues like the one observed where a tool internally calls getParsedCommandLineOfConfigFile with a relative path, but then sets the current working directory incorrectly, causing the wrong file to be loaded.
Choose names that clearly communicate the purpose, meaning, and scope of variables, functions, constants, and files. Names should be self-documenting and immediately convey their role to other developers.
For constants, use descriptive names that explain what they represent:
// Instead of unclear naming
const APIKind = apiKind
// Use descriptive enum-style naming
type APIKind uint64
const (
UseSonicJson = iota
UseStdJson
)
For function parameters, ensure names accurately reflect their semantic meaning rather than just their type. For file names, avoid unnecessary specificity when broader scope is appropriate (e.g., loader_windows.go instead of loader_windows_amd64.go when architecture constraint isn’t needed).
The goal is to make code self-documenting through meaningful names that reduce the need for additional comments or context to understand their purpose.
When handling errors in your code, always provide detailed context in error messages to aid debugging. Use Kotlin’s standard error handling functions instead of directly throwing exceptions or using non-null assertions.
error() function with descriptive messages instead of throwing exceptions directly:
```kotlin
// Bad
if (companion.isCompanion == false) {
throw AssertionError() // Unhelpful message
}// Good if (companion.isCompanion == false) { error(“Expected $companion to be a companion object”) }
2. When reporting errors from caught exceptions, preserve cause information:
```kotlin
// Bad
catch (e: IllegalStateException) {
reportError(e.message!!)
throw CompilationErrorException()
}
// Good
catch (e: IllegalStateException) {
reportError("${e.message}\nCaused by: ${e.cause?.javaClass?.name}: ${e.cause?.message}")
throw CompilationErrorException()
}
// Good val fieldName = getterCall.symbol.owner.name.getFieldName() ?: error(“Expected getter call to have a field name”)
4. Include relevant context in assertion messages:
```kotlin
// Bad
assert(companion.isCompanion)
// Good
assert(companion.isCompanion) { "Expected $companion to be a companion object" }
By following these practices, you’ll make your code more maintainable and debugging much easier when errors inevitably occur.
Improve code readability by simplifying complex conditional expressions and control flow structures. Factor out repeated boolean expressions from nested conditions, use early returns instead of result variables when possible, and consolidate related conditional blocks.
Key practices:
Example of factoring out boolean expressions:
// Instead of:
if ((!is_empty && ts_node_end_byte(node) <= self->start_byte) ||
(!is_empty && point_lte(ts_node_end_point(node), self->start_point)))
// Use:
if (!is_empty && (
ts_node_end_byte(node) <= self->start_byte ||
point_lte(ts_node_end_point(node), self->start_point)))
Example of consolidating conditionals:
// Instead of separate error checks:
if (e == PARENT_DONE) {
cleanup();
return TSQueryErrorSyntax;
}
if (e) {
cleanup();
return e;
}
// Use unified approach:
if (e) {
cleanup();
if (e == PARENT_DONE) e = TSQueryErrorSyntax;
return e;
}
Choose names that follow established API conventions and guidelines to create a consistent, intuitive codebase:
as_* for cheap, reference-based conversions
// Prefer this
fn as_socket(&self) -> socket2::SockRef<'_> {
socket2::SockRef::from(self)
}
// Instead of
fn to_socket(&self) -> socket2::SockRef<'_>
// Prefer this
pub fn sender_strong_count(&self) -> usize
// Instead of
pub fn strong_count(&self) -> usize
// Prefer this
pub fn notify_one_last_in(&self)
// Instead of
pub fn notify_one_lifo(&self)
// Prefer this
pub async fn copy_bidirectional_with_sizes<A, B>
// Instead of
pub async fn copy_bidirectional_with_size<A, B>
Deref// Prefer this
pub fn clone_inner(&'static self) -> T
// Instead of
pub fn clone(&'static self) -> T // Confusing with Clone trait
Even when verbosity increases, prioritize clarity and prevention of API confusion. AbortOnDropHandle may be verbose, but it clearly communicates the type’s behavior.
Choose explicit and unambiguous names over concise but unclear ones. When a method, type, or variable has a specific behavior or purpose, the name should clearly communicate that, even if it results in longer names.
For example:
AsyncFdTryNewError instead of a generic error name to clearly indicate its purposeAbortOnDropHandle over shorter alternatives to communicate the type’s behaviorsender_strong_count() over strong_count() when the context requires clarity about which entity’s count is being returnednotify_one_last_in() rather than notify_one_lifo() to make the behavior more obviouscopy_bidirectional_with_sizes() instead of copy_bidirectional_with_size() when it takes multiple size parametersclone_inner() over clone() to avoid confusion with the standard Clone trait implementationWhile conciseness has value, clarity and explicitness should take precedence when there’s potential for ambiguity or confusion. This principle applies to all identifiers including variables, methods, functions, types, and modules.
When implementing algorithms, break down complex methods that handle multiple concerns into smaller, more focused methods. This improves maintainability, makes edge cases easier to handle, and allows for more flexible reuse of algorithm components.
For example, instead of having a single method that handles multiple responsibilities:
static gboolean
get_common_simd_info (MonoClass *vector_klass, MonoMethodSignature *csignature,
MonoTypeEnum *atype, int *vector_size, int *arg_size, int *scalar_arg)
{
// Complex logic handling multiple concerns:
// 1. Getting size information
// 2. Determining element type
// 3. Finding scalar arguments
// ...
}
Break it into focused methods with clear responsibilities:
static gboolean
get_common_simd_info (MonoClass *klass, MonoTypeEnum *atype,
int *klass_size, int *arg_size)
{
// Focus only on getting class size and element type information
}
static int
get_common_simd_scalar_arg (MonoMethodSignature *csignature)
{
// Focus only on finding scalar arguments
}
This approach makes algorithms more adaptable to changing requirements, such as supporting additional intrinsics or handling different types of operations. It also simplifies testing and debugging by isolating specific functionality into well-defined methods with clearer purposes.
When designing APIs with related method pairs (such as send/receive, input/output, or request/response), ensure their signatures and parameter structures mirror each other for consistency and developer ergonomics.
Related API methods should have symmetric interfaces that make their relationship clear and predictable. This reduces cognitive load and prevents confusion about how data flows between paired operations.
Example of inconsistent API:
// Inconsistent - send takes separate parameters, callback receives combined object
comm.send(data, buffers);
comm.onMessage(({data, buffers}) => { ... });
Example of consistent API:
// Consistent - both use the same parameter structure
comm.send(data, buffers);
comm.onMessage((data, buffers) => { ... });
This principle applies to any paired operations where data structure or parameter patterns should logically correspond, ensuring developers can easily understand and predict the API behavior based on one method when using its counterpart.
When storing string pointers from external sources (like command-line arguments), always validate the input and create managed copies to prevent null references and lifetime issues. This practice ensures that your stored references remain valid throughout your program’s execution and prevents potential crashes from null or invalid pointers.
Follow these steps when handling external string inputs:
// Unsafe approach:
kotlin::programName = argv[0]; // May cause crashes if argc=0 or lifetime issues later
// Safe approach:
if (argc > 0 && argv[0][0] != '\0') {
kotlin::programName = strndup(argv[0], 4096); // Creates bounded copy with independent lifetime
}
This approach prevents crashes from invalid pointers, protects against potential buffer issues by limiting string length, and eliminates lifetime concerns by managing your own copy of the data.
When configuring dependencies in package manifests, prioritize efficiency and maintainability by avoiding redundant dependencies, using flexible versioning, and explicitly managing features. Before adding new dependencies, check if existing ones already provide the required functionality. Use version ranges (like “^1.0”) instead of exact pins to allow compatible updates while maintaining stability. When configuring features, disable defaults when not needed and explicitly specify required features for better control.
Example from Cargo.toml:
# Good: Use existing dependency instead of adding new one
dirs = "3.0" # Already provides home_dir functionality
# Good: Flexible versioning for compatibility
cc = "^1.0"
# Good: Explicit feature management
regex = { version = "1.10.4", default-features = false, features = ["perf", "unicode"] }
This approach reduces dependency bloat, improves build reliability, and makes version management more predictable across different environments.
Always add new tests to the current recommended test infrastructure rather than legacy systems that are being phased out. This reduces technical debt and ensures tests remain maintainable as the codebase evolves.
When adding a new test:
Example:
// Instead of adding to legacy build.gradle:
// standaloneTest("new_feature") {
// source = "runtime/new_feature/test.kt"
// }
// Add a proper test class to the new infrastructure:
// e.g., in native/native.tests/tests/org/jetbrains/kotlin/konan/test/blackbox/NewFeatureTest.kt
class NewFeatureTest : AbstractNativeTest() {
@Test
fun testNewFeature() {
// Test implementation
}
}
For some test types, you may need to run generation tasks after adding new test data files.
When modifying existing APIs, preserve original parameter types and return signatures to avoid breaking client code. Even when internal implementations change or new functionality is added, the public interface should remain stable for existing consumers.
For example, when extending a Logger to support multiple outputs, maintain the original single-writer constructor:
// Keep original API intact
func New(out io.Writer, prefix string, flag int) *Logger {
// Internal implementation can adapt to new multi-writer design
}
// Add new functionality separately if needed
func NewMultiWriter(outs []io.Writer, prefix string, flag int) *Logger {
// New API for enhanced functionality
}
Similarly, when changing return types, ensure the new type is compatible with existing usage patterns or maintain the original signature alongside new variants. This approach protects existing integrations while allowing API evolution.
When documenting code examples and shell commands in project documentation, follow these formatting standards to ensure consistency and clarity:
shell rather than bash for shell commands that work in any shell environmentcargo install --locked cargo-docs-rs
text to distinguish them from executable commandscd tokio
cargo fuzz list
fuzz_linked_list
--locked with installation commands to prevent dependency issuescargo install --locked cargo-spellcheck
These practices improve documentation readability and help users distinguish between what they should type and what they should expect to see as output.
When documenting shell commands in technical documentation, use the shell language identifier instead of bash as commands are typically relevant to any shell. Separate commands and their output into different code blocks to make it clearer what users should actually input. This improves readability and prevents users from accidentally copying output text as if it were commands.
For example, instead of:
$ cd tokio
$ cargo fuzz list
fuzz_linked_list
Use:
cd tokio
cargo fuzz list
fuzz_linked_list
When documenting tool installation commands, include the --locked flag to protect users from dependency issues:
cargo install --locked cargo-docs-rs
This ensures developers can reproduce the exact same environment for development tools.
Tests should be written explicitly with concrete examples and clear assertions rather than being generated programmatically or hidden in utility code. This improves test readability, maintainability, and debugging capabilities.
Key principles:
Example of preferred approach:
// Instead of generated tests in loops
#[test]
fn test_consecutive_zero_or_modifiers() {
// Prefer explicit test cases
let query1 = Query::new(language, "(comment)*** @capture").unwrap();
let query2 = Query::new(language, "(comment)??? @capture").unwrap();
// With specific assertions about expected behavior
assert_eq!(matches1.len(), 3);
assert_eq!(matches2.len(), 1);
}
// Add assertions to doc tests
//! let tree = parser.parse(code, None).unwrap();
//! assert!(!tree.root_node().has_error());
This approach makes tests easier to understand, debug, and maintain while ensuring that test behavior is predictable and well-documented.
When using atomic operations, explicitly justify the choice of memory ordering. Each use of a memory ordering should be documented with a clear explanation of why that specific ordering is sufficient and safe.
Key guidelines:
Example:
impl<T> Clone for Sender<T> {
fn clone(&self) -> Self {
// Relaxed is sufficient for incrementing the reference count
// since no synchronization is needed - the sender is already
// guaranteed to be valid when Clone is called
self.shared.ref_count.fetch_add(1, Relaxed);
Self { shared: self.shared.clone() }
}
}
impl<T> Drop for Sender<T> {
fn drop(&mut self) {
// AcqRel ordering required for reference count decrement
// to synchronize with other threads that may be checking
// if this is the last sender
if self.shared.ref_count.fetch_sub(1, AcqRel) == 1 {
self.shared.close();
}
}
}
Choose names that clearly express intent and follow established conventions. Prefer explicit, descriptive names over abbreviations or ambiguous terms. Align with platform-specific naming patterns and maintain consistency with existing codebase conventions.
Key guidelines:
BREADTH_FIRST over BFS)reifiedTypeParameters for properties, getReifiedTypeParameters() for functions in Kotlin)argumentType instead of typeArgument)child instead of inner)Example:
// Incorrect
enum class WalkAlgorithm {
BFS, // Abbreviated
DFS // Abbreviated
}
// Correct
enum class WalkAlgorithm {
BREADTH_FIRST, // Explicit
DEPTH_FIRST // Explicit
}
When designing public APIs, prioritize extensibility while maintaining backward compatibility and implementation hiding. This ensures APIs can evolve without breaking existing clients while allowing for future enhancements.
Key principles:
Example:
// Instead of enum-based priority
enum class Priority { LOW, NORMAL, HIGH } // Difficult to extend
// Use comparable class
class Priority(val value: Int) : Comparable<Priority> {
companion object {
val LOW = Priority(0)
val NORMAL = Priority(1000)
val HIGH = Priority(2000)
}
override fun compareTo(other: Priority): Int =
value.compareTo(other.value)
}
// This allows third parties to insert custom priorities
val CUSTOM = Priority(1500) // Between NORMAL and HIGH
This approach:
Always explicitly configure socket blocking behavior when creating TCP connections, and provide compile-time flags for backwards compatibility when changing default behaviors.
When establishing TCP connections, don’t rely on implicit socket blocking settings. Instead, explicitly set the blocking mode after connection creation. When changing default socket behavior, use compile-time conditional compilation to allow users to opt into the previous behavior if needed.
Example implementation:
mut conn := &TcpConn{
sock: s
read_timeout: net.tcp_default_read_timeout
write_timeout: net.tcp_default_write_timeout
}
$if !net_nonblocking_sockets ? {
conn.set_blocking(true)!
}
This pattern ensures that:
-d net_nonblocking_socketsApply this approach consistently across all TCP connection creation points including dial_tcp(), dial_tcp_with_bind(), and TcpListener.accept().
Choose semantic, logical names that describe purpose and intent rather than physical characteristics or implementation details. This improves code maintainability, internationalization support, and clarity.
For CSS properties, prefer logical properties over directional ones:
// Instead of physical directions
'margin-left', 'margin-right'
// Use logical directions
'margin-inline-start', 'margin-inline-end'
'inset-inline-start', 'inset-inline-end'
For API parameters and types, use precise, descriptive names that clearly communicate expected values:
// Instead of generic types
setMargin(edge: Edge, margin: number | string): void
// Use specific, semantic types
setMargin(edge: Edge, margin: number | 'auto' | `${number}%`): void
This approach creates more robust, internationally-friendly code that better expresses developer intent and reduces ambiguity about expected values or behavior.
The eval() function executes arbitrary JavaScript code from strings, creating significant security vulnerabilities including code injection attacks. This is especially dangerous when processing external data like API responses, user input, or browser logs that could contain malicious code.
Use safer alternatives for common use cases:
Example of the security issue:
// Dangerous - can execute arbitrary code
const result = eval(logs[0].message.replace(/^[^"]*/, ''));
// Safe - only parses JSON data
const result = JSON.parse(logs[0].message.replace(/^[^"]*/, ''));
Modern linters and security scanners typically flag eval() usage. If eval() is absolutely necessary, ensure the input is thoroughly validated and sanitized, though this approach is strongly discouraged.
Base performance-critical constants, limits, and thresholds on empirical benchmarking rather than arbitrary values. When implementing optimizations that involve numeric parameters—such as buffer size limits, inlining costs, or caching thresholds—conduct systematic benchmarking to determine optimal values and document the rationale.
For memory management decisions, establish limits that balance performance gains with resource efficiency:
func cacheEncodeState(e *encodeState) {
// Threshold determined through benchmarking to balance
// memory efficiency vs. performance gains
if e.Buffer.Cap() > 32768 {
return // Don't cache large buffers
}
// ... cache the state
}
const (
// 57 was benchmarked to provide most benefit with no bad surprises
inlineExtraCallCost = 57
// These values were benchmarked to provide most benefit
inlineBigForCost = 105
)
When creating benchmarks, separate different scenarios to get granular performance data rather than combining multiple cases. This enables more precise optimization decisions and helps identify the specific conditions where performance improvements matter most.
When implementing algorithms that involve spacing, gaps, or iterative calculations, carefully verify loop bounds and edge cases to prevent off-by-one errors. These errors commonly occur when calculating spacing between N items (which requires N-1 gaps, not N gaps) or when applying operations to boundary elements.
Key areas to scrutinize:
Example from gap calculation:
// Wrong: adds gap for all children (N gaps for N items)
for (int i = 0; i < childCount; i++) {
mainDim += childSize + betweenMainDim;
}
// Correct: adds gap only between children (N-1 gaps for N items)
const bool isLastChild = i == childCount - 1;
if (isLastChild) {
betweenMainDim -= gap; // Remove gap for last item
}
Always validate your algorithm with boundary cases: single item, empty collection, and maximum expected size. Consider whether conditional checks like if (lineCount > 1) are truly necessary or artifacts of avoiding edge case bugs.
Error messages should be both specific about what went wrong and provide contextual information to help developers locate and fix the issue. Even in performance-critical or low-level APIs, basic validation with clear error messages is essential because “there’s no easy way for the developer to get feedback on failures” otherwise.
When validation fails, provide specific details about the invalid input and acceptable ranges or formats. Additionally, include contextual information such as stack traces to help developers identify the source of the error.
Example of good error messaging:
// Specific error with details about what's wrong and what's expected
throw new RangeError(`The status provided (${status}) is not equal to 101 and outside the range [200, 599].`);
// Adding contextual information to warnings
console.warn(
"Prototype access via __proto__ attempted; __proto__ is not implemented in Deno due to security reasons. Use Object.setPrototypeOf instead.",
new Error().stack
);
This approach balances performance considerations with developer experience by providing minimal but meaningful validation and clear guidance on how to resolve issues.
When implementing operations that manipulate collections or perform repeated actions, carefully consider the time complexity implications. Avoid operations that could result in unexpected quadratic or worse complexity. Pay special attention to:
Example of problematic implementation:
// BAD: O(n²) complexity
fun <E> ArrayList<E>.addAll(index: Int, elements: Collection<E>) {
var i = index
elements.forEach { element ->
array.asDynamic().splice(i, 0, element) // Shifts array on each insert
i++
}
}
// BETTER: O(n) complexity
fun <E> ArrayList<E>.addAll(index: Int, elements: Collection<E>) {
// Single array modification with all elements
array.asDynamic().splice(index, 0, *elements.toTypedArray())
}
Choose data structures based on your specific use case:
Build systems and configuration scripts must respect standard environment variables and provide proper override mechanisms. This ensures flexibility for users with different build environments, cross-compilation needs, and custom toolchain requirements.
Always use the ?= operator in Makefiles to set defaults while allowing environment overrides, and use override += to append mandatory flags:
# Allow environment override with sensible defaults
CFLAGS ?= -O3 -Wall -Wextra
CC ?= clang
# Append mandatory flags that cannot be overridden
override CFLAGS += -std=gnu11 -fPIC -Ilib/include
For configuration detection, provide compiler flag overrides as escape hatches:
// Allow compile-time override while providing automatic detection
#if !defined(TS_BIG_ENDIAN)
#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#define TS_BIG_ENDIAN 1
#else
#define TS_BIG_ENDIAN 0
#endif
#endif
Never hardcode or remove the ability to override critical build variables like CC, CFLAGS, CXXFLAGS, or LDFLAGS. Users may need to specify custom compilers, cross-compilation toolchains, or additional flags for their specific build environments. This principle applies to all build systems including Makefiles, CMake, and custom build scripts.
Function documentation comments must follow Go conventions by starting with the function name, and all parameters should have clear explanations of their purpose and current functionality.
In Go, documentation comments for functions should begin with the function name followed by a description of what it does. Additionally, when functions have parameters that may not be immediately clear, especially those with evolving functionality, include comments that explain their current purpose.
Example of proper function documentation:
// WithCompileRecursiveDepth sets the depth of recursive compile for decoder and encoder.
func WithCompileRecursiveDepth(depth int) CompileOption {
// implementation
}
// OnObjectBegin handles the beginning of a JSON object value with a
// suggested capacity that can be used to make your custom object container.
// Currently, capacity primarily indicates whether this is an empty node.
func OnObjectBegin(capacity int) error {
// implementation
}
This ensures that developers can quickly understand function behavior and parameter usage, while also documenting current limitations or evolving functionality for future reference.
Always validate array and string bounds before accessing memory to prevent out-of-bounds access and potential crashes. When working with strings or arrays, especially those with known length information, check indices against the actual length before dereferencing pointers.
Key practices:
Example from the codebase:
// Before accessing pos[i], always check bounds
if (i >= src->len) {
return ERR_INVAL; // Handle out-of-bounds case
}
// In string comparison, use length-bounded operations
static int _strcmp(const char *p, const char *q, size_t max_len) {
size_t i = 0;
while (i < max_len && *p && *q && *p == *q) {
p++; q++; i++;
}
// Safe comparison within bounds
}
This prevents accessing invalid memory addresses that could lead to undefined behavior, crashes, or security vulnerabilities.
Maintain consistent naming conventions throughout the codebase and use semantically clear identifiers that clearly indicate their purpose and behavior. All functions, variables, and other identifiers should follow the same naming style (preferably snake_case for C code) and have descriptive names that make their functionality obvious.
Key principles:
Example improvements:
// Before: Mixed styles and unclear names
bool isSpace(char a);
bool isInteger(char a);
int charToNum(char c);
bool compare(MapPair lhs, MapPair rhs);
// After: Consistent snake_case and clear semantics
bool is_space(char a);
bool is_integer(char a);
int char_to_num(char c);
bool less_than(MapPair lhs, MapPair rhs);
This ensures code readability and maintainability by eliminating confusion about naming patterns and making function purposes immediately clear to other developers.
Always validate array and buffer bounds before accessing elements to prevent out-of-bounds access and potential security vulnerabilities. This is critical in algorithms that iterate through data structures or perform string operations.
The pattern should be: check bounds first, then access content. Never assume data is within bounds without explicit validation.
Example of incorrect approach:
// Dangerous - no bounds check
while(is_space(pos[i])) {
i++;
}
Example of correct approach:
// Safe - bounds check before content check
while(!is_overflow(i, srclen) && is_space(pos[i])) {
i++;
}
In string comparison algorithms, ensure you don’t continue comparing beyond the shorter string’s length. When the compared portions are equal, the longer string should be considered greater without accessing potentially invalid memory locations.
This principle applies to all array-based algorithms including parsing, sorting, searching, and string manipulation operations.
Maintain consistent indentation and brace alignment throughout the codebase to improve readability and code organization. Use 4-space indentation consistently across all files and align closing braces with their corresponding opening statements.
Key formatting requirements:
Example of proper formatting:
while(pos[i] !='\0'){
if(k==arr->cap){
*p = i+1;
return ERR_RECURSE_MAX;
}
while(is_space(pos[i])){
i++;
}
}
Consistent formatting makes code easier to read, debug, and maintain. It also helps team members quickly understand code structure and reduces cognitive load when reviewing or modifying code.
Comments should explain the reasoning behind code decisions rather than restating what the code obviously does. Focus on the “why” rather than the “what” to provide value that isn’t immediately apparent from reading the code itself.
For placement, longer explanatory comments should be written on separate lines above the relevant code block. API documentation and function descriptions should be positioned above the function declaration, not inline or scattered throughout the implementation.
Example of poor commenting:
while(isSpace(pos[i])){ // If there is a space before the beginning, eat the space first
i++;
}
if(pos[i] != '['){ // If the first one is not a left bracket, returning it directly is illegal
*p = i+1; // P points to the first position after the error
arr->len = 0;
return ERR_INVAL;
}
Example of improved commenting:
// Skip leading whitespace to handle malformed input gracefully
while(isSpace(pos[i])){
i++;
}
// Array format must start with '[' - reject invalid syntax early
if(pos[i] != '['){
*p = i+1;
arr->len = 0;
return ERR_INVAL;
}
For API functions, place comprehensive documentation above the function:
// Parses a JSON-like array string into GoIntSlice structure
// Returns error code on invalid format, updates position pointer
long decode_u64_array(const GoString* src, long* p, GoIntSlice* arr){
// implementation...
}
Regularly review and update version specifications in configuration files to align with current standards and internal practices. When updating versions, consider both the benefits of newer features and potential compatibility impacts.
Configuration files should specify reasonably current versions of tools, languages, and frameworks rather than outdated minimums. However, version bumps should be planned carefully, especially for major version changes that might affect compatibility.
Ensure consistency across different build systems and package managers. If updating one configuration file, check related configurations to maintain alignment.
Example from Package.swift:
// Consider updating from older versions
swift-tools-version:5.3 // → swift-tools-version:5.8
cxxLanguageStandard: .cxx14 // → .cxx17 (when appropriate)
// Also check related configurations like CocoaPods
// to maintain consistency across build systems
Before updating, evaluate the trade-offs between staying current and maintaining stability, especially in widely-used libraries where breaking changes can impact many users.
Enhance code clarity and maintainability by applying several readability techniques: extract repeated expressions into well-named variables, simplify complex boolean logic, use proper constant declarations, and remove redundant code that sets default values.
Key practices:
mainAxisDim for node->getLayout().measuredDimensions[dim[mainAxis]])constexpr bool kConstantName = false; over preprocessor defines or global variablesYGJustifyFlexStart is the default for justify content)Example of extracting repeated expressions:
// Before
child->getLeadingPosition(mainAxis, node->getLayout().measuredDimensions[dim[mainAxis]])
child->getLeadingMargin(mainAxis, node->getLayout().measuredDimensions[dim[mainAxis]])
// After
const auto mainAxisDim = node->getLayout().measuredDimensions[dim[mainAxis]];
child->getLeadingPosition(mainAxis, mainAxisDim)
child->getLeadingMargin(mainAxis, mainAxisDim)
When modifying existing APIs, maintain backwards compatibility by adding new overloads rather than directly changing existing signatures. Mark old signatures as deprecated when introducing new patterns, but keep them functional until a major version change.
Example: Instead of breaking change:
// Before
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[];
// Don't directly modify to:
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, preferences: UserPreferences): RenameLocation[];
Add deprecation with overload:
// Do this instead:
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, preferences: UserPreferences): RenameLocation[];
/** @deprecated Pass `providePrefixAndSuffixTextForRename` as part of a `UserPreferences` object. */
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[];
This approach:
When creating error messages that include information from underlying errors, use fmt.Errorf with the %w verb instead of string concatenation with err.Error(). This preserves the error chain, enabling proper error unwrapping and better debugging capabilities.
String concatenation breaks the error chain and prevents tools and code from accessing the original error:
// Bad: breaks error chain
return nil, errors.New("x509: failed to serialize extensions attribute: " + err.Error())
// Good: preserves error chain
return nil, fmt.Errorf("x509: failed to serialize extensions attribute: %w", err)
The wrapped error can then be unwrapped using errors.Unwrap() or checked with errors.Is() and errors.As(). Consider documenting when your functions return errors that implement Unwrap() []error method to make the unwrapping behavior explicit to callers.
Maintain consistent and standardized build configurations across the project to improve maintainability and reduce errors:
Centralize dependency management - Use shared dependency declarations rather than hardcoding versions in individual module build files. This ensures consistency across the project and makes version updates simpler.
// Instead of:
testImplementation("com.google.code.gson:gson:2.8.9")
// Prefer using centralized dependency management:
testImplementation(commonDependency("com.google.code.gson:gson"))
Avoid duplicating configurations - Don’t repeat configuration code that’s already defined in parent or root projects. Reference or inherit from common configurations instead.
Prefer Kotlin DSL - For new modules, use .gradle.kts (Kotlin) scripts rather than .gradle (Groovy) scripts to benefit from type safety, better IDE support, and consistency with the broader codebase.
By following these practices, you’ll reduce configuration drift, make updates more manageable, and maintain a more consistent build environment.
Ensure build configurations are consistently propagated through all build steps and output directories follow standardized patterns. This prevents configuration mismatches that can cause deployment issues and makes CI/CD pipelines more reliable.
Key practices:
Example from CMake integration:
<Exec Command="cmake -S . -B build -DCMAKE_INSTALL_PREFIX=binnative -DCMAKE_BUILD_TYPE='$(Configuration)' && cmake --build build --target install --config $(Configuration)" />
Example for consistent output directories:
<OutDir>bin\$(PlatformTarget)\$(Configuration)\</OutDir>
This standardization ensures that debug/release configurations are properly handled across the entire build pipeline and that all artifacts end up in predictable locations for packaging and deployment.
Ensure CI/CD workflows use technology-specific setup actions and execute commands in the correct working directories. Avoid using generic or unrelated setup actions that install unnecessary toolchains, as this increases build time and complexity. Always specify the appropriate working directory for technology-specific commands to ensure they execute in the correct context.
For example, replace generic setup actions with technology-specific ones:
# Instead of using setup-js for C# builds
- name: Setup
uses: ./.github/actions/setup-js
# Use C#-specific setup
- name: Setup
uses: ./.github/actions/setup-cs
And ensure commands run in the correct directory:
- name: Restore Workloads
run: dotnet workload restore
working-directory: csharp
This practice improves build reliability, reduces unnecessary dependencies, and makes CI configuration more maintainable and understandable.
Always measure and document the impact of performance optimizations before implementing them. When making optimization decisions, quantify the benefits (size reduction, speed improvement) against the costs (build time, code complexity, maintainability).
Key practices:
Example from compiler optimizations:
# Measured impact: 10kb size reduction
-fno-exceptions -fno-rtti # Disables C++ features for size savings
-flto # Link-time optimization for performance (2x build time)
Example from memory optimization:
// Before: 12 bytes overhead per node (4 + 8 bytes)
struct YGNode {
uint32_t gDepth = 0; // 4 bytes per node
YGNodeRef pRoot = nullptr; // 8 bytes per node
};
// After: Pass values through function parameters instead
void calculateLayout(YGNode* node, uint32_t depth, uint32_t generation);
This approach ensures optimizations provide real value and helps teams make informed decisions about performance trade-offs.
When implementing security measures around JavaScript’s __proto__ property, disable the setter to prevent prototype pollution attacks while carefully considering whether getter access is needed for compatibility. Prototype pollution occurs when attackers can modify Object.prototype, potentially affecting all objects in the application.
Use Object.defineProperty to disable the setter while optionally preserving getter functionality:
// Disables setting `__proto__` and emits a warning instead, for security reasons.
Object.defineProperty(Object.prototype, "__proto__", {
get: Object.prototype.__proto__, // Keep getter if needed for compatibility
set: function() {
console.warn("Setting __proto__ is disabled for security reasons");
}
});
Consider the compatibility impact on dependencies that may rely on __proto__ functionality. In environments where no user code executes (like TypeScript compilation), completely removing __proto__ access may be appropriate. For runtime environments, emitting warnings helps identify problematic dependencies while maintaining security.
Implement automated release workflows to publish updates frequently and consistently across all distribution channels. Manual release processes create bottlenecks that force consumers to pin dependencies to specific commit SHAs instead of using stable releases.
Set up CI/CD pipelines that automatically publish to all relevant package managers (crates.io, NPM, GitHub releases) after each merged PR or batch of changes. This reduces maintenance overhead and ensures consumers can rely on regular releases rather than tracking development commits.
Example automation approach:
# .github/workflows/release.yml
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Publish to crates.io
run: cargo publish
- name: Publish Node.js bindings to NPM
run: npm publish
- name: Create GitHub release with artifacts
run: gh release create v${{ env.VERSION }}
Frequent automated releases eliminate the need for consumers to pin to commit SHAs and ensure that fixes and improvements reach users promptly without requiring manual intervention from maintainers.
When writing network-related code, always use named constants from system packages instead of magic numbers, and ensure complete protocol implementation. This includes using proper constants for network operations (like windows.IfOperStatusUp instead of 0x01), calculating buffer sizes correctly with system functions (like syscall.CmsgSpace(4) for file descriptor passing), and setting absolute deadlines with time.Now().Add() rather than relative durations.
Additionally, avoid making assumptions about network infrastructure - for example, don’t assume req.TLS != nil indicates HTTPS when TLS termination might occur at a load balancer. Always implement complete test scenarios that actually exercise the network paths being tested.
Example of proper constant usage:
// Bad: magic number
if aa.OperStatus != 0x01 {
continue
}
// Good: named constant
if aa.OperStatus != windows.IfOperStatusUp {
continue
}
// Bad: hardcoded buffer size
oob := make([]byte, 32)
// Good: calculated buffer size
oob := make([]byte, syscall.CmsgSpace(4))
This approach makes network code more maintainable, portable, and less prone to subtle bugs related to protocol assumptions or system-specific values.
Configure automated formatting tools in CI/CD pipelines to enforce code style standards rather than silently fix violations. Use validation flags that cause the build to fail when formatting issues are detected, and ensure all commands run non-interactively.
For installation commands, always include flags to avoid user prompts:
run: sudo apt-get install -y clang-format-${{ inputs.version }}
For formatting validation, use flags that check without modifying files and exit with error codes:
run: npx --yes google-java-format --set-exit-if-changed --dry-run --glob=java/**/*.java
This approach catches style violations early in the development process and maintains consistent code formatting across the entire codebase by making compliance mandatory rather than optional.
Before implementing thread safety mechanisms like atomic operations or mutexes for global variables, critically assess whether perfect synchronization is actually required for program correctness. Consider the performance cost of synchronization against the importance of data accuracy.
Many global variables, especially counters or flags, may not require strict synchronization if approximate or eventually consistent values are sufficient for the use case. For example, instance counters used only for testing or debugging may not justify the synchronization overhead.
Example of unnecessary synchronization:
// Before: Adding atomic for test-only counter
std::atomic<int32_t> gNodeInstanceCount(0); // Causes bus overhead
// After: Conditional compilation or accepting approximate values
#ifdef YOGA_TESTING
int32_t gNodeInstanceCount = 0; // Only precise in single-threaded tests
#endif
Example of questioning atomic necessity:
// Before: Automatic atomic usage
std::atomic<uint8_t> gCurrentGenerationCount{0};
// Consider: Does increment accuracy matter?
// If the purpose is just to invalidate caches and trigger recalculations,
// occasional missed increments may be acceptable and avoid synchronization costs
uint8_t gCurrentGenerationCount = 0; // Simple increment, accepts race conditions
Key evaluation criteria:
This approach prevents over-engineering thread safety solutions and maintains better performance in concurrent systems.
Design APIs that align with platform conventions and user expectations rather than creating novel patterns. APIs should feel natural to developers already familiar with the target platform’s idioms and established libraries.
When designing public interfaces, prioritize consistency with existing platform patterns over internal implementation convenience. For example, if your platform typically uses individual properties for layout values, follow that pattern even if it creates a larger API surface:
// Preferred: Matches UIKit conventions
@property (nonatomic) CGFloat paddingLeft;
@property (nonatomic) CGFloat paddingTop;
@property (nonatomic) CGFloat paddingStart; // RTL support
// Rather than novel approaches that break familiar patterns
- (void)setPadding:(CGFloat)padding forEdge:(YGEdge)edge;
Additionally, keep internal implementation details private. Helper functions and utilities should not be exposed in public headers just because they’re convenient for internal use:
// Wrong: Internal helpers in public API
template<typename T, typename... A>
T* YGAllocate(A&&... arguments) { /* internal helper */ }
// Right: Keep internal helpers in private headers
Make APIs explicit and unambiguous by requiring all necessary parameters rather than providing multiple variants. This forces developers to think about important decisions like units, reduces API surface area, and prevents ambiguous usage patterns.
Ensure all code follows the established project formatting standards consistently across the codebase. This includes adhering to specific indentation rules, import statement formatting, and other style conventions defined for the project. When contributing code, always match the existing formatting patterns rather than introducing inconsistencies.
For example, if the project uses four-space indentation and single-quote import packages:
//go:build !amd64
package sonic
import (
'reflect' // single quote import
)
func example() {
// four space indentation
if condition {
doSomething()
}
}
Additionally, remove unused or debug code entirely rather than leaving it commented out, as this keeps the codebase clean and maintainable. Consider using automated formatting tools like gofmt to ensure consistency and reduce manual formatting errors.
Avoid using not-null assertions (!!) when safer alternatives exist. Instead:
?.) when accessing properties or methods on nullable references?:) to provide default valuesFor example, instead of:
val result = nullableValue!!.doSomething()
Prefer:
// Safe call with elvis operator
val result = nullableValue?.doSomething() ?: defaultValue
// Or early return with null check
if (nullableValue == null) {
return defaultValue // or handle the null case appropriately
}
val result = nullableValue.doSomething()
This pattern reduces the risk of NullPointerExceptions and makes code more robust. When reviewing code, look for unnecessary not-null assertions that could be replaced with safer constructs, as shown in the UnnecessaryNotNullAssertionFix class. Additionally, follow the pattern seen in the expression typing code where early returns are used when a descriptor is null.
Choose identifiers that accurately represent their purpose, semantics, and relationship to the codebase. Names should be self-documenting and consistent with established patterns in the module or API.
Key principles:
force_inline instead of redefining inline)borrowed_item vs owned_item)Entry for non-collection elements)ts_language_ consistently)0x25A1 instead of decimal 9633 for Unicode values)Example of good naming:
// Good: Clear ownership semantics
static inline void analysis_state_set__push_by_clone(
AnalysisStateSet *self,
AnalysisState *borrowed_item // Clearly indicates caller retains ownership
) {
// ...
}
// Good: Consistent API prefix and self-documenting hex value
int ts_language_symbol_type_is_named(const TSLanguage *self, TSSymbol typeId) {
// Use 0x25A1 instead of 9633 for Unicode box character □
while (iswspace(lexer->lookahead) || 0x25A1 == lexer->lookahead) {
// ...
}
}
Names should be semantically meaningful and properly prefixed to prevent namespace conflicts. In C, enum variants and global identifiers are added to the top-level namespace, so prefix them with their type name even if repetitive. Choose names that reflect domain concepts and purpose rather than technical implementation details. Prioritize clarity over brevity by spelling out words fully.
Examples:
TSQuantifierOneOrMore instead of OneOrMoreshift (domain-specific) instead of status (generic)ts_malloc_default instead of ts_malloc_dfltSUBTREE_SIZE (semantic) instead of SUBTREE_4_BYTES (technical)This prevents naming conflicts, improves code readability, and makes the codebase more maintainable by clearly indicating the purpose and scope of each identifier.
Identify and cache the results of expensive computations to avoid redundant calculations, especially in performance-critical code paths. This includes recursive functions, user-defined callbacks, and complex arithmetic operations that may be called multiple times with the same inputs.
When reviewing code, look for:
Example of caching expensive baseline calculations:
// Before: Calling YGBaseline multiple times
if (alignItem == YGAlignBaseline) {
leadingCrossDim += collectedFlexItemsValues.maxBaselineAscent - YGBaseline(child);
}
// Later in code...
someOtherValue = YGBaseline(child); // Redundant call
// After: Cache the result in child layout
// At algorithm start: child->getLayout().cachedBaseline = YGBaseline(child);
if (alignItem == YGAlignBaseline) {
leadingCrossDim += collectedFlexItemsValues.maxBaselineAscent - child->getLayout().cachedBaseline;
}
This optimization is particularly important for functions that are recursive, call user-defined callbacks, or are executed in tight loops where performance matters most.
Always ensure proper cleanup of resources in tests to prevent memory leaks. When using EmbeddedChannel, call readOutbound() to process all expected buffers and finish() at the end of your test to verify no resources remain and release any remaining buffers. This is particularly important for tests involving data buffers, channels, and I/O operations.
Example:
@Test
public void testBufferHandling() {
EmbeddedChannel channel = new EmbeddedChannel(new YourHandler());
// Write test data
channel.writeInbound(Unpooled.wrappedBuffer(testData));
// Read and verify all expected results
ByteBuf result1 = channel.readInbound();
assertEquals(expectedData1, result1);
result1.release();
ByteBuf result2 = channel.readInbound();
assertEquals(expectedData2, result2);
result2.release();
// Ensure channel is properly closed and no resources are leaked
assertFalse(channel.finish());
}
This pattern prevents resource leaks that can accumulate during test runs and ensures that your tests properly validate the complete processing of data.
Platform-specific code and API usage should be centralized in designated configuration files rather than scattered throughout the codebase. When adding code that might not be available on all supported platforms:
check_symbol_exists or similar mechanismsFor example, instead of embedding platform checks throughout your code:
// Not recommended: platform-specific code scattered in implementation
void* SystemNative_AlignedAlloc(uintptr_t alignment, uintptr_t size)
{
#if defined(__APPLE__)
// macOS-specific implementation
#elif defined(__linux__)
// Linux-specific implementation
#endif
}
// Recommended: Use centralized feature detection
#include "pal_config.h" // Contains centrally defined HAVE_* macros
void* SystemNative_AlignedAlloc(uintptr_t alignment, uintptr_t size)
{
#if HAVE_ALIGNED_ALLOC
return aligned_alloc(alignment, size);
#elif HAVE_POSIX_MEMALIGN
void* result = NULL;
return posix_memalign(&result, alignment, size) == 0 ? result : NULL;
#else
// Fallback implementation for platforms without native support
return malloc(size);
#endif
}
This approach improves maintainability, makes configuration audits easier, and centralizes knowledge about platform differences.
When adding new dependencies to build.gradle.kts files, always update the corresponding verification metadata to prevent build failures. Use the Gradle command ./gradlew --write-verification-metadata md5,sha256 help to generate the necessary metadata, but review the output carefully as it may add more dependencies than required. Consider manually adding only the necessary verification entries if the automated command adds excessive dependencies.
Example workflow:
// 1. Add dependency to build.gradle.kts
dependencies {
implementation("io.github.java-diff-utils:java-diff-utils:4.10")
}
// 2. Generate verification metadata
// Run: ./gradlew --write-verification-metadata md5,sha256 help
// 3. Review generated changes in verification-metadata.xml and retain only necessary entries
<component group="io.github.java-diff-utils" name="java-diff-utils" version="4.10">
// Keep only required verification entries
</component>
Always document non-obvious security implementations, especially authentication mechanisms, with explanatory comments and references to underlying implementation details or documentation. Security-related code should be explicit and clear to prevent misunderstandings that could lead to vulnerabilities.
When implementing credential handling or other security features in ways that might not be immediately obvious to other developers, add comments that explain your design decisions and link to relevant documentation or implementation details.
Example:
// Authentication is implemented following the pattern used in org.eclipse.aether.repository.Authentication
// where all credentials are provided to the builder and consumers decide what to use
// See: org.eclipse.aether.transport.wagon.WagonTransporter#getProxy for implementation details
setAuthentication(
AuthenticationBuilder().apply {
with(options) {
addUsername(username?.let(::tryResolveEnvironmentVariable))
addPassword(password?.let(::tryResolveEnvironmentVariable))
}
}
)
Ensure go:build directives follow correct syntax with no space between “//” and “go”, and use proper logical operators for multiple conditions. Incorrect spacing causes the directive to be ignored by the compiler, potentially including wrong code for target environments.
For multiple platforms, use OR operators (||) between conditions:
//go:build aix || darwin || dragonfly || freebsd || netbsd || openbsd || solaris
For complex conditions, use parentheses and logical operators:
//go:build (js && wasm) || windows
Common mistakes include adding spaces (“// go:build”) or using comma-separated lists without operators. Always verify build constraints are properly recognized by checking compilation behavior across target platforms.
Ensure all documentation comments follow proper formatting conventions and are placed where developers will most likely read them. Documentation should use correct grammar, complete sentences, and end with periods. Additionally, place documentation at the most relevant location - typically at interface definitions rather than implementation details, since developers read interface documentation when implementing methods.
Example of proper formatting:
// ErrTimeout is a fake timeout error.
var ErrTimeout = errors.New("timeout")
Example of proper placement: Place WriteTo documentation at the WriterTo interface definition, not at Copy function implementation, because developers implementing WriteTo will read the interface documentation, not the Copy function documentation.
When logging information about the same operation or context, combine multiple related log statements into a single, comprehensive message rather than using separate log calls. This reduces log noise, improves readability, and can enhance performance by reducing the number of logging function calls.
Instead of multiple separate log statements:
LOG("start_token chars:%lu", self->current_position.chars);
LOG("start_token row:%lu", self->current_point.row);
LOG("start_token column:%lu", self->current_point.column);
Consolidate into a single informative message:
LOG("start_token chars:%lu, row:%lu, column:%lu",
self->current_position.chars,
self->current_point.row,
self->current_point.column);
Additionally, remove unnecessary or accidental logging parameters that don’t provide value, as this simplifies logging macros and reduces maintenance overhead. Focus on logging only the information that is actually useful for debugging or monitoring purposes.
Variable names should follow established language conventions and clearly communicate their purpose and context. Avoid abbreviated or cryptic names in favor of descriptive identifiers that make code self-documenting.
Key principles:
err instead of e for errors in Go)writeSocket instead of parent-reads)Example of improvement:
// Poor naming
r, e := http.Post("http://127.0.0.1/count", "text/plain", strings.NewReader("POST data"))
writeFile := os.NewFile(uintptr(fds[0]), "parent-reads")
// Better naming
req, err := http.Post("http://127.0.0.1/count", "text/plain", strings.NewReader("POST data"))
writeSocket := os.NewFile(uintptr(fds[0]), "write-socket")
This approach improves code readability, reduces cognitive load for other developers, and aligns with community standards and expectations.
Always use Gradle’s Provider API when accessing project, system, or Gradle properties in your build configuration. This ensures proper handling of configuration cache and allows for dynamic property updates.
Instead of directly accessing properties:
// DON'T
val verbose = (project.hasProperty("kapt.verbose") &&
project.property("kapt.verbose").toString().toBoolean() == true)
// DO
val verbose = project.providers.gradleProperty("kapt.verbose")
.map { it.toBoolean() }
.orElse(false)
// DON'T
val systemProp = System.getProperty("my.property")
// DO
val systemProp = project.providers.systemProperty("my.property")
This approach:
When dealing with file paths or build directory locations, use ProjectLayout:
val layout = project.layout
val outputFile = layout.buildDirectory.file("output/file.txt")
When defining configuration flags and options, use practices that ensure compatibility across different preprocessors and build environments. For macro-based configuration flags, use integer values (0/1) instead of boolean literals (true/false) to avoid preprocessor compatibility issues. Additionally, consider using runtime configuration mechanisms (like config objects or flags) instead of compile-time options when flexibility is needed.
Example of safer macro definitions:
// Safer - use integer values
#define PRINT_CHANGES 0
#define PRINT_SKIPS 0
// Or use properly defined constants
#define PRINT_CHANGES FALSE // where FALSE is defined as 0
// Instead of potentially problematic boolean literals
#define PRINT_CHANGES false // may cause preprocessor issues
For configuration mechanisms, prefer runtime configuration over compile-time when possible:
// Better - runtime configuration via config object
YGConfig config;
config.callMeasureCallbackOnAllNodes = true;
// Instead of compile-time only
#ifdef YOGA_CALL_MEASURE_CALLBACK_ON_ALL_NODES
This approach ensures better portability across different compilers and preprocessors while providing more flexibility for users of your code.
When a function can determine an early result based on input validation or special cases, return immediately rather than assigning to a temporary variable and having a single exit point. Early returns reduce nesting, improve readability, and often result in more efficient code by making guard conditions more visible.
// Recommended:
static int convert_prio(const int prio)
{
if (prio == CPUPRI_INVALID)
return CPUPRI_INVALID;
// Continue with normal processing
return prio - 40;
}
// Avoid:
static int convert_prio(int prio)
{
int cpupri;
if (prio == CPUPRI_INVALID)
cpupri = CPUPRI_INVALID;
else
cpupri = prio - 40;
return cpupri;
}
Early validation helps prevent unnecessary operations when preconditions aren’t met, and results in flatter code that’s easier to understand and maintain. Using const for parameters that won’t be modified also improves code clarity and can help the compiler optimize.
Before modifying initialization patterns or removing static variables, investigate the semantic meaning of undefined values and object lifetimes in the existing codebase. What appears to be a memory leak or improper initialization may be intentional design.
Key considerations:
YGUndefined) may be semantically different from zero initializationExample of proper investigation:
// Before changing this:
static YGConfigRef defaultConfig = YGConfigNew(); // Not a leak - process lifetime
// Or this:
memset(&(node->getLayout()), 0, sizeof(YGLayout));
// Investigate: Does the constructor initialize differently?
node->getLayout() = {}; // May initialize with YGUndefined instead of 0
// If tests break, understand why before forcing zero values
Always verify that your changes preserve the intended semantics, especially when dealing with undefined states, static lifetimes, or special sentinel values.
Always make code declarations explicit and consistent to improve readability and maintainability. This includes specifying all property attributes, using const for immutable parameters, and avoiding misleading naming conventions.
Key practices:
Example of explicit property declaration:
// Good - explicit attributes
@property (nonatomic, readwrite, assign) CGFloat flexGrow;
// Avoid - relying on defaults
@property (nonatomic) CGFloat flexGrow;
Example of consistent const usage:
// Good - const parameters
WIN_EXPORT void YGSetExperimentalFeatureEnabled(const YGConfigRef config,
const YGExperimentalFeature feature,
const bool enabled);
// Avoid - missing const
WIN_EXPORT void YGSetExperimentalFeatureEnabled(YGConfigRef config,
YGExperimentalFeature feature,
bool enabled);
This approach reduces ambiguity, makes code intentions clear, and helps prevent accidental modifications while improving overall code consistency.
Ensure Go projects use modern module configuration instead of legacy GOPATH setup. Projects should be created outside of GOPATH directory structure, with proper environment variable configuration and module files.
Key requirements:
go mod initExample project structure:
~/myproject/
hello/
go.mod # module file
hello.go # command source
stringutil/
reverse.go # package source
This approach eliminates GOPATH dependency, simplifies project setup, and aligns with modern Go development practices. The configuration ensures compatibility across different Go versions while providing a cleaner development experience.
Always add appropriate safety checks before operations that could result in undefined behavior, null references, or memory issues. This includes using weak references to prevent retain cycles, verifying exact object types before type-specific operations, and adding conditional checks before calling methods that may return undefined values.
Examples of safety checks to implement:
@property (nonatomic, weak) id<YGLayoutEntity> entity;_isUIView = [view isMemberOfClass:[UIView class]];if (!view.yoga.isUIView || [view.subviews count] > 0) { /* safe to call sizeThatFits */ }These patterns prevent crashes, memory leaks, and unexpected behavior by ensuring operations are only performed when it’s safe to do so.
When developing parsers or grammars, apply strategic optimization by prioritizing commonly used language features and choosing appropriate conflict resolution algorithms based on performance trade-offs.
Focus development effort on the most frequently used language constructs rather than attempting comprehensive coverage initially. As noted in parser development practices: “Most languages have a long-tail of features that don’t get utilized frequently. It is reasonable to prioritize developing 80% of a language and only supporting commonly used elements.”
For handling parsing conflicts, choose resolution strategies based on their computational complexity:
Example conflict resolution approaches:
// Compile-time: Use precedence to resolve operator conflicts
precedence: [
['left', '+', '-'],
['left', '*', '/'],
]
// Runtime: Allow parser to handle ambiguity dynamically
conflicts: $ => [
[$.expression, $.statement]
]
This optimization strategy balances development velocity with parser performance, ensuring efficient resource allocation while maintaining practical functionality for the most common use cases.
Add clear explanatory comments to complex calculations, platform-specific behavior, or non-obvious code sections. Use NOTE comments to explain the reasoning behind tricky implementations, especially when dealing with framework-specific quirks or mathematical calculations.
This practice improves code maintainability and helps other developers understand the rationale behind complex logic. Comments should explain the “why” rather than the “what”, particularly for code that might seem counterintuitive or handles edge cases.
Example:
// NOTE: The center is offset by the layer.anchorPoint, so we have to take it into account.
view.center = (CGPoint) {
.x = YGRoundPixelValue(x + (width * view.layer.anchorPoint.x)),
.y = YGRoundPixelValue(y + (height * view.layer.anchorPoint.y)),
};
// NOTE: We must set only the bounds' size and keep the origin.
// UIScrollView's bounds.origin is not .zero when a contentOffset is set.
view.bounds = (CGRect) {
.origin = view.bounds.origin,
.size = {
.width = YGRoundPixelValue(width),
.height = YGRoundPixelValue(height),
},
};
Before implementing convenience features or making architectural decisions, carefully evaluate their performance implications and hidden costs. Consider factors like method call overhead, automatic behavior frequency, and resource utilization patterns.
Key considerations:
Example from layout systems:
// Instead of automatic dirty detection on every layout:
// YGSize newSize = YGMeasureView(node, 0, YGMeasureModeUndefined, 0, YGMeasureModeUndefined);
// Consider the cost: "every leaf node is going to be measured during every single layout call"
// Prefer explicit control when performance matters:
@interface YGLayout ()
@property (nonatomic, assign, readonly) YGNodeRef node; // ivar reduces msgSends
@end
Always benchmark assumptions and consider the cumulative impact of seemingly small optimizations, especially in performance-critical paths like layout and rendering.
When asserting conditions on potentially null pointers, always check for null first before accessing the pointer’s properties or members. This prevents segmentation faults and makes null expectations explicit in the code.
Instead of writing assertions that could crash on null pointers:
assert(root->ref_count > 0); // Crashes if root is NULL
Use a combined assertion that checks null first:
assert(root && root->ref_count > 0); // Safe and explicit
This pattern serves two purposes: it prevents crashes during debugging when assertions are enabled, and it clearly documents that the code expects the pointer to be non-null. When removing null checks, ensure the underlying assumptions about null safety have actually changed and document why the check is no longer needed.
Always validate inputs and handle edge cases before performing algorithmic operations, especially when dealing with indices, state transitions, or data structure traversals. Check for boundary conditions, invalid states, and null/undefined values that could cause out-of-bounds access or undefined behavior.
Key practices:
Example from parse table operations:
// Before: Direct access without validation
*state_index = new_state_ids[*state_index];
// After: Validate before accessing
if (*state_index != (ParseStateId)(-1))
*state_index = new_state_ids[*state_index];
// Also ensure meaningful fields exist
if ((action.type == ParseActionTypeShift && !action.extra) ||
action.type == ParseActionTypeRecover)
fn(&action.state_index);
This prevents crashes from invalid indices and ensures algorithms only operate on valid data, making code more robust and maintainable.
Configuration properties should behave predictably without hidden side effects that override explicitly set values. When a developer sets a configuration value, the system should respect that value rather than applying additional logic that might change the effective behavior.
Avoid implementing getters that can return different values than what was explicitly set, as this creates confusion and makes the system harder to reason about. If complex logic is needed to determine effective configuration state, consider separating the user-facing configuration from the computed state.
Example of problematic pattern:
- (BOOL)isEnabled
{
if (!super.isEnabled) {
return NO; // Side effect: can return NO even if explicitly set to YES
}
// Additional complex logic here...
}
Instead, use clear separation between configuration and computed state:
- (BOOL)isEnabled
{
return _isEnabled; // Returns exactly what was set
}
- (BOOL)isIncludedInLayout
{
return self.isEnabled && [self meetsOtherCriteria]; // Separate computed property
}
This approach makes the configuration behavior transparent and the codebase easier to maintain and debug.
Design logging utility functions to return formatted strings rather than directly performing logging operations. This separation of concerns improves modularity, testability, and allows callers to control when and how logging occurs.
Instead of having utility functions that directly log:
void YGLogAlign(YGLogLevel logLevel, const char * param, YGAlign value){
char * text = "undefined";
switch(value){
case YGAlignAuto: text = "auto"; break;
case YGAlignCenter: text = "center"; break;
// ...
}
if(param == NULL) {
YGLog(logLevel, text); // Direct logging
}
}
Prefer functions that return formatted strings:
const char* YGAlignToString(YGAlign value) {
switch(value){
case YGAlignAuto: return "auto";
case YGAlignCenter: return "center";
// ...
default: return "undefined";
}
}
// Caller controls logging: YGLog(logLevel, YGAlignToString(value));
This approach reduces coupling, makes the formatting logic reusable in non-logging contexts, and simplifies testing of string conversion logic independently from logging behavior.
All identifiers including parameters, files, functions, and categories should clearly communicate their purpose and role. Avoid generic or ambiguous names that require additional context to understand.
For parameters, use descriptive names that indicate what the parameter represents:
// Poor: generic parameter name
void YGLogAlign(YGLogLevel logLevel, const char * param, YGAlign value)
// Better: descriptive parameter name
void YGLogAlign(YGLogLevel logLevel, const char * propertyName, YGAlign value)
Maintain consistency between related components - if a file is named YGEnumsPrint.h, the functions should use Print terminology rather than mixing Print and Log.
For categories and interfaces, ensure the name accurately reflects the current functionality. If an interface evolves beyond being “Private” to include public properties needed by tests, update the name accordingly:
// When functionality changes, update the name to match
@interface YGLayout () // No longer just private
This approach improves code readability and reduces the cognitive load for developers trying to understand the codebase.
Function names should clearly convey their purpose and behavior, not just their signature or implementation details. Avoid names that could be confused with existing functions that have similar but different behavior.
When naming functions, consider what the function actually accomplishes rather than how it accomplishes it. For example, YGValueToFloat describes the conversion but not the purpose, while YGValueResolve better describes what the function is trying to achieve.
Additionally, be careful when adding functions with similar names to existing ones. If YGNodeMarkDirtyInternal already handles recursion, naming another function YGNodeMarkDirtyRecursive creates confusion about which function to use and their behavioral differences.
Example of improvement:
// Less descriptive - focuses on conversion
static inline float YGValueToFloat(const YGValue unit, const float parentSize)
// More descriptive - focuses on purpose
static inline float YGValueResolve(const YGValue unit, const float parentSize)
Always use braces for control structures (if, for, while, etc.) even for single statements, and maintain consistent spacing around parentheses and braces. This improves code readability and prevents potential bugs when code is modified later.
Use proper spacing with a space after the keyword and before the opening parenthesis, and place the opening brace on the same line with a space before it:
// Good
if (attrs != null) {
layoutParams = new LayoutParams(context, attrs);
}
// Avoid
if(attrs != null){
layoutParams = new LayoutParams(context, attrs);
}
// Avoid (missing braces)
if (attrs != null)
layoutParams = new LayoutParams(context, attrs);
This standard ensures consistent formatting across the codebase and makes code easier to read and maintain.
Always use explicit, well-defined representations for undefined, uninitialized, or invalid states instead of magic values or allowing undefined behavior to propagate silently. Create named constants or enum values for undefined states, and make conscious decisions about whether to crash, return defaults, or handle undefined values gracefully.
Key practices:
0.0f, reference defined constants like YGDefaultFlexGrow to prevent bugs and improve clarityYGValueUndefined rather than raw undefined values or magic numbers-1 or other magic numbers to represent invalid states, consider explicit enum values, though weigh API design implicationsExample of good practice:
// Good: Named constant for undefined state
static const YGValue YGValueUndefined = {YGUndefined, YGUnitUndefined};
node->style.flexBasis = YGValueUndefined;
// Good: Explicit handling decision
if (YGFloatIsUndefined(baseline)) {
// Crash fast - undefined baseline indicates a bug
YGAssert(false, "Baseline function must not return undefined");
}
// Avoid: Magic values
node->style.flexBasis.value = YGUndefined; // Raw undefined
layout->cached_layout.width_measure_mode = (css_measure_mode_t)-1; // Magic -1
This approach prevents bugs by making undefined states visible and intentional, while forcing explicit decisions about how to handle edge cases.
When encountering complex conditional expressions or generic variable references, extract them into clearly named variables or use explicit identifiers to improve code readability and maintainability.
For complex conditions, break them down into descriptive boolean variables:
// Instead of:
childCrossMeasureMode = YGFloatIsUndefined(childCrossSize) ||
(currentRelativeChild->resolvedDimensions[dim[crossAxis]]->unit == YGUnitAuto)
? YGMeasureModeUndefined : YGMeasureModeExactly;
// Extract to:
bool hasUndefinedSize = YGFloatIsUndefined(childCrossSize);
bool hasAutoUnit = currentRelativeChild->resolvedDimensions[dim[crossAxis]]->unit == YGUnitAuto;
childCrossMeasureMode = (hasUndefinedSize || hasAutoUnit)
? YGMeasureModeUndefined : YGMeasureModeExactly;
For generic references, prefer explicit, context-specific identifiers:
// Instead of:
return node->layout.measuredDimensions[dim[crossAxis]];
// Use explicit:
return node->layout.measuredDimensions[YGDimensionHeight];
This practice makes code self-documenting, easier to debug, and reduces cognitive load when reading complex logic.
Ensure proper resource allocation and cleanup pairing, and maintain clear ownership semantics to prevent memory leaks and null reference issues. When allocating resources like GCHandle, always pair allocation with corresponding cleanup calls. Avoid overwriting allocated resources without first cleaning up the previous allocation.
Additionally, use return types that clearly indicate ownership. Return IntPtr for references you don’t own, and use strongly-typed handles only when your code manages the resource lifecycle.
Example of problematic code:
public void SetMeasureFunction(MeasureFunction measureFunction) {
var handle = GCHandle.Alloc(this); // Never freed!
Native.YGNodeSetContext(_ygNode, GCHandle.ToIntPtr(handle));
}
public void SetBaselineFunction(BaselineFunction baselineFunction) {
var handle = GCHandle.Alloc(this); // Overwrites previous without cleanup!
Native.YGNodeSetContext(_ygNode, GCHandle.ToIntPtr(handle));
}
Better approach: Track allocated handles and free them before allocating new ones, or use return types like IntPtr when you don’t own the underlying resource to avoid confusion about lifecycle responsibilities.
Tests should focus on validating one specific behavior or scenario rather than combining multiple test cases into complex, multi-purpose tests. Each test should have a clear, meaningful purpose that actually exercises the code under test.
Avoid creating tests that are too complex or that test trivial scenarios where the expected behavior is obvious. When tests fail, focused tests make it much easier to identify and debug the specific issue.
For example, instead of creating one large test case that validates multiple alignment behaviors:
<!-- Avoid: Complex test testing multiple things -->
<div id="complex_alignment_test" style="width: 150px; height: 100px; flex-wrap: wrap; flex-direction: row; align-content: stretch; justify-content: space-between;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
Create separate, focused tests:
<!-- Prefer: Focused test for align-content stretch -->
<div id="align_content_stretch_test" style="width: 130px; height: 100px; flex-wrap: wrap; align-content: stretch;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
<!-- Separate test for justify-content space-between -->
<div id="justify_content_space_between_test" style="width: 130px; height: 100px; justify-content: space-between;">
<div style="width: 50px; height: 10px;"></div>
<div style="width: 50px; height: 10px;"></div>
</div>
Additionally, ensure tests actually validate meaningful scenarios. Avoid creating tests where the expected behavior is trivial or where there’s no actual space to distribute or meaningful layout differences to observe.
When implementing layout algorithms, ensure strict adherence to web standards and specifications, particularly regarding control flow and edge case handling. Complex algorithms like baseline calculation require precise implementation of specification details to avoid subtle bugs.
Key considerations:
break vs continue) to match specification behaviorExample from baseline calculation:
// Wrong: continues past first line, skipping valid children
if (child->lineIndex > 0) {
continue; // This skips children incorrectly
}
// Correct: breaks at first line boundary as per spec
if (child->lineIndex > 0) {
break; // Only consider first line children
}
This prevents algorithmic errors that can cause incorrect layout behavior and ensures compatibility with web standards.
Ensure all identifiers (properties, methods, classes, variables) follow the established naming conventions of the programming language being used. This improves code readability, maintainability, and consistency across the codebase.
For example, in C#:
MeasureOutput instead of measureOutput"CSSLayout" instead of "CSSLayout.dll"Language conventions exist for good reasons - they make code more predictable for other developers familiar with the language, improve IDE support and tooling integration, and maintain consistency with standard libraries and frameworks. When naming conflicts arise, prioritize following the language’s established patterns over personal preferences or porting convenience.
Use explicit strict equality checks when testing for null or undefined values. Prefer x === undefined over typeof x === 'undefined' when the variable is known to exist. When checking for both null and undefined, use x == null (loose equality) as it covers both cases, or check each explicitly with x === null || x === undefined. Avoid using the || operator for fallback values when legitimate falsy values (like 0, false, or empty strings) are expected, as they will incorrectly trigger the fallback.
// Good - explicit undefined check
if (value !== undefined) {
return value;
}
// Good - covers both null and undefined
if (value != null) {
return value;
}
// Bad - unnecessary typeof when variable exists
if (typeof value !== 'undefined') {
return value;
}
// Bad - loses legitimate falsy values
return node.style.marginTop || node.style.margin; // 0 becomes fallback value
// Good - explicit existence check
return 'marginTop' in node.style ? node.style.marginTop : node.style.margin;
Maintain consistent formatting and structural choices throughout the codebase to improve readability and maintainability. This includes always using braces for control structures (even single-line statements), choosing appropriate control flow constructs for clarity, and organizing code to prevent execution order issues.
Key practices:
Example of consistent brace usage:
// Good - always use braces
if (!node.lastLayout) {
node.lastLayout = {};
}
// Avoid - inconsistent formatting
if (!node.lastLayout) node.lastLayout = {};
Example of clearer control structure:
// Preferred for simple cases
if (typeof val === 'number') {
obj[key] = roundNumber(val);
} else if (typeof val === 'object') {
inplaceRoundNumbersInObject(val);
}
// Instead of switch for simple type checking
These consistent choices make code more predictable for team members and reduce cognitive load during code reviews.
Algorithms should handle their own state initialization and cleanup internally rather than relying on callers to manage it. This prevents bugs caused by stale data from previous executions and reduces the burden on calling code.
When designing algorithms that maintain state between calls or process hierarchical data structures, ensure that:
Example from layout algorithm:
function layoutNode(node, parentMaxWidth, parentDirection) {
// Algorithm handles its own state reset internally
if (!node.lastLayout) {
node.lastLayout = {};
}
// Reset child layouts internally rather than requiring caller to do it
// This prevents stale position data from affecting current calculations
resetChildLayouts(node);
// Continue with algorithm logic...
}
This approach prevents issues where “isUndefined checks might fail where they shouldn’t” and ensures “the callsite doesn’t have to care and possibly introduce bugs” by forgetting to reset state properly.
Always measure and validate performance optimizations through benchmarking before assuming they provide benefits. Performance changes can have unexpected consequences, and what appears to be an optimization may actually degrade performance in real-world scenarios.
When implementing performance optimizations:
Example approach:
// Before assuming this is faster:
return node.style[key] || node.style[type + key + suffix];
// Benchmark with realistic test cases:
// "in order to make sure that it has performance improvement, we need to benchmark it.
// Can you use the code that generates a random test to generate a big random tree
// and then time the layout algorithm before and after? Otherwise we can't know
// if this is a performance optimization or not."
This is especially important for layout algorithms and frequently-called code paths where performance assumptions can significantly impact application responsiveness.
Build scripts should avoid performing operations as side effects and instead use explicit, dedicated tasks for each operation. Side effects make build processes unpredictable, harder to maintain, and difficult to debug when issues arise.
Instead of creating directories, cleaning files, or performing setup operations implicitly within other tasks, create dedicated tasks with clear responsibilities. This approach improves build reliability, makes the build process more transparent, and allows for better control over execution order.
For example, rather than creating a dist folder as a side effect in the main build configuration:
// Avoid this - side effect
module.exports = function(grunt) {
// Create the dist folder if it doesn't exist. It is deleted by the 'clean' task.
if (!fs.existsSync('dist')) {
fs.mkdirSync('dist');
}
// rest of configuration...
};
Use explicit tasks instead:
// Prefer this - explicit task
grunt.registerTask('ensure-dirs', function() {
if (!fs.existsSync('dist')) {
fs.mkdirSync('dist');
}
});
grunt.registerTask('build', ['ensure-dirs', 'other-tasks']);
This makes the build process more predictable and allows the clean task to properly handle dist/** patterns without worrying about implicit directory creation elsewhere.
Consolidate all configuration variables, constants, and settings into a centralized configuration object rather than declaring them as scattered variables throughout the codebase. This improves maintainability, reduces duplication, and enables consistent templating patterns.
Instead of defining variables separately:
var cTestClean, cTestCompile, cTestExecute;
var pathDelimiter = path.delimiter;
Add them to your config object:
var config = {
libName: 'css-layout',
distFolder: 'dist',
srcFolder: 'src',
cTestClean: '...',
cTestCompile: '...',
cTestExecute: '...',
pathDelimiter: path.delimiter
};
This allows for consistent templating usage like <%= config.pathDelimiter %> instead of mixing template strings with concatenation, and keeps all configuration in one discoverable location.
When implementing feature flags, ensure consistency between runtime and compiled scenarios. Feature switches marked with FeatureSwitchDefinition must properly return the AppContext switch value when defined:
// INCORRECT implementation
[FeatureSwitchDefinition("System.Net.SocketsHttpHandler.Http3Support")]
public static bool IsHttp3Supported => SomeDefaultLogic();
// CORRECT implementation - must use the AppContext switch value
[FeatureSwitchDefinition("System.Net.SocketsHttpHandler.Http3Support")]
public static bool IsHttp3Supported =>
AppContext.TryGetSwitch("System.Net.SocketsHttpHandler.Http3Support", out var ret) ? ret : SomeDefaultLogic();
Consider dead code elimination implications when designing feature flags for AOT scenarios, as behavior differences may arise between dotnet run and dotnet publish. Avoid adding configuration switches preemptively - only introduce them when there’s a demonstrated need from users. For platform-specific features, ensure configuration is properly respected across all supported environments.