R11BREAKINGfunctionSync to Async
Flags when a synchronous function becomes async. The return type changes from T to Promise<T>, breaking all callers that expect a synchronous return value.
Applies to
TypeScriptPythonGoJavaRust
Why it matters
Adding the
async keyword changes the function's return type from T to Promise. Every caller that uses the return value directly (const x = fn()) will now get a Promise object instead of the actual value. This is a silent logical bug if the caller doesn't await the result.Example
Before (sync)
// validator.ts
export function validate(input: string): boolean {
return schema.test(input);
}
// caller.ts
if (validate(userInput)) {
proceed();
}After (async)
// validator.ts (BREAKING)
export async function validate(input: string): Promise<boolean> {
const schema = await loadSchema();
return schema.test(input);
}
// caller.ts — NOW A BUG!
// validate() returns Promise<boolean>, which is always truthy
if (validate(userInput)) {
proceed(); // ALWAYS runs — Promise object is truthy
}What you see in the terminal
$ npx dg check
[BREAKING] validate (signature_change)
src/validator.ts:2
Function changed from sync to async. Return type is now Promise<boolean> instead of boolean. Callers using the return value without await will receive a Promise object.How detection works
The classifier checks the async boolean on both signatures. If the old signature was not async and the new one is, the rule fires.
Real-world scenario
A validation function is changed from sync to async because it now needs to load a schema from a database. The `if (validate(input))` check in 15 call sites now always evaluates to `true` because a Promise object (even one that resolves to false) is truthy in JavaScript.
Edge cases
- If the function already returned Promise
without the async keyword, this rule may not fire - Python's def to async def is caught by this rule
- In Go, adding a channel return is conceptually similar but not detected by this rule