This document explains our best practices. Follow these best practices when you're working on our code.
Branch names should start with a Conventional Commits scope like feat/
or fix/
.
If you're closing an issue with your PR then put the issue number after the scope.
Finally, describe the changes in the branch in a few words.
Some good branch names:
feat/13732-cacache-cleanup
fix/15431-gitea-automerge-strategy
refactor/jest-reset-mocks
docs/rewrite-packageRules-section
Avoid branch names like patch-1
.
If you don't know the correct Conventional Commit scope: give your branch a descriptive name like issue-1-feature-foo
.
If you forgot to pick a good branch name when you started work, then rename the branch before creating the pull request. Read the GitHub Docs, renaming a branch to learn how to rename your branch on GitHub.
- Prefer full function declarations for readability and better stack traces, so avoid
const func = ():void => {}
- Prefer
interface
overtype
for TypeScript type declarations - Avoid Enums, use unions or immutable objects instead
- Always add unit tests for full code coverage
- Only use
istanbul
comments for unreachable code coverage that is needed forcodecov
completion - Use descriptive
istanbul
comments
- Only use
- Avoid cast or prefer
x as T
instead of<T>x
cast - Prefer
satisfies
operator overas
, read the TypeScript release notes forsatisfies
operator to learn more - Avoid
Boolean
instead useis
functions from@sindresorhus/is
package, for example:is.string
// istanbul ignore next: can never happen
- Use
function foo(){...}
to declare named functions - Use function declaration instead of assigning function expression into local variables (
const f = function(){...}
) (TypeScript already prevents rebinding functions)- Exception: if the function accesses the outer scope's
this
then use arrow functions assigned to variables instead of function declarations
- Exception: if the function accesses the outer scope's
- Regular functions should not access
this
, but arrow functions and methods may accessthis
- Only use nested functions when the lexical scope is used
Avoid:
bar(function(){...})
Use:
bar(() => {
this.doSomething();
});
Generally the this
pointer should not be rebound.
Only use function expressions if you need to dynamically rebind this
.
Source: Google TypeScript Style Guide, function declarations.
Write code that is easier to read, review and maintain. Avoid "clever" code that's hard to understand.
Prefer verbose code which is easier for others to read and maintain than concise code which may be hard or slower for others to understand.
For example, Array reduce()
functions are often hard to understand first time, and can be replaced with simpler for
loops.
A for
loop is just as fast but is simpler to understand and maintain.
Single purpose functions are easier to understand, test and debug.
function caller() {
// ..code..
calculateUpdateAndPrint(data)
// ..code..
}
function calculateUpdateAndPrint(...) { /* code */ }
Simplified code:
function caller() {
// code..
const res = calculate(data);
update(res);
print(res);
// code..
}
function calculate(...) { /* code */ }
function update(...) { /* code */ }
function print(...) { /* code */ }
Fail quickly. Nested code logic is hard to read and prone to logic mistakes.
function foo(str: string): boolean {
let result = false;
if (condition(str)) {
const x = extractData(str);
if (x) {
// do something
result = true;
}
}
return result;
}
Simplified code:
function foo(str: string): boolean {
if (!condition(str)) {
return false;
}
const x = extractData(str);
if (!x) {
return false;
}
// do something
return true;
}
Refactor the code or refactor the tests.
Avoid refactoring the code and tests at the same time, this can mask regression errors.
For WARN
, ERROR
and FATAL
log messages use logger metadata.
Also use logger metadata the result is a complex metadata object needing a multiple-line pretty stringification.
For INFO
and DEBUG
log messages inline the metadata into the log message where feasible.
It is OK to not inline metadata if it's complex, but in that case first think whether that much information really needs to be logged.
WARN
, ERROR
and FATAL
messages are often used in metrics or error catching services.
These log messages should have a static msg
component, so they can be automatically grouped or associated.
Good:
logger.debug({ config }, 'Full config');
logger.debug(`Generated branchName: ${branchName}`);
logger.warn({ presetName }, 'Failed to look up preset');
Avoid:
logger.debug({ branchName }, 'Generated branchName');
logger.warn(`Failed to look up preset ${presetName}`);
Avoid the Array()
constructor, with or without new
, in your TypeScript code.
It has confusing and contradictory usage.
So you should avoid:
const a = new Array(2); // [undefined, undefined]
const b = new Array(2, 3); // [2, 3];
Instead, always use bracket notation to initialize arrays, or from
to initialize an Array with a certain size.
For example:
// [0, 0, 0, 0, 0]
Array.from<number>({ length: 5 }).fill(0);
Use for ( ... of ...)
loops instead of [Array|Set|Map].prototype.forEach
and for ( ... in ...)
.
- Using
for ( ... in ...)
for objects is error-prone. It will include enumerable properties from the prototype chain - Using
for ( ... in ...)
to iterate over arrays, will counterintuitively return the array's indices - Avoid
[Array|Set|Map].prototype.forEach
. It makes code harder to debug and defeats some useful compiler checks like reachability
Only use Array.prototype.map()
when the return value is used, otherwise use for ( ... of ...)
.
Source: Google TypeScript Style Guide, iterating objects
Use named exports in all code.
Avoid default exports
.
This way all imports
follow the same pattern.
Source: Google TypeScript Style Guide, exports
Use ES6 module syntax. For example:
import { square, diag } from 'lib';
// You may also use:
import * as lib from 'lib';
And avoid require
:
import x = require('...');
Prefer using Http
from util/http
to simplify HTTP request handling and to enable authentication and caching, as our Http
class will transparently handle host rules.
For example:
import { Http } from '../../../util/http';
const http = new Http('some-host-type');
try {
const body = (await http.getJson<Response>(url)).body;
} catch (err) {
...
}
Never use Promise.resolve
in async functions.
Never use Promise.reject
in async functions, instead throw an Error
class type.
Use the Luxon
package to handle dates and times.
Use UTC
to be time zone independent.
if (end) {
const now = DateTime.now().toUTC();
const eol = DateTime.fromISO(end, { zone: 'utc' });
return eol < now;
}
- Separate the Arrange, Act and Assert phases with newlines
- Use
it.each
rather thantest.each
- Prefer Tagged Template Literal style for
it.each
, Prettier will help with formatting- See Example
- Mock Date/Time when testing a Date/Time dependent module
- For
Luxon
mocking see Example
- For
- Prefer
jest.spyOn
for mocking single functions, or mock entire modules- Avoid overwriting functions, for example: (
func = jest.fn();
)
- Avoid overwriting functions, for example: (
- Prefer
toEqual
- Use
toMatchObject
for huge objects when only parts need to be tested - Avoid
toMatchSnapshot
, only use it for:- huge strings like the Renovate PR body text
- huge complex objects where you only need to test parts
- Avoid exporting functions purely for the purpose of testing unless you really need to
- Avoid cast or prefer
x as T
instead of<T>x
cast- Use
partial<T>()
fromtest/util
if only a partial object is required
- Use
Use the Fixture
class to load fixtures.
For example:
Fixture.get('./file.json'); // for loading string data
Fixture.getJson('./file.json'); // for loading and parsing objects
Fixture.getBinary('./file.json'); // for retrieving a buffer
Declare types and function prototypes with JSDoc.
- Use Typescript getter setters (Accessors) when needed.
The getter must be a
pure function
i.e.- The function return values are identical for identical arguments
- The function has no side effects
- Omit constructors when defining Static classes
- No
#private
fields, use TypeScript's visibility annotations instead - Avoid underscore suffixes or prefixes like
_prop
, instead use whole words as the suffix or prefix likeinternalProp
Use Named Capturing Groups when capturing multiple groups, for example: (?<groupName>CapturedGroup)
.
We recommend you set core.autocrlf = input
in your Git config.
You can do this by running this Git command:
git config --global core.autocrlf input
This prevents the carriage return \r\n
which may confuse Renovate.
You can also set the line endings in your repository by adding * text=auto eol=lf
to your .gitattributes
file.