Files
renovate/lib/util/common.ts
Sebastian Poxhofer c32e2391a0 refactor(linters): add prefer-coerce-object rule (#45493)
Refs #45216

The object counterpart to `prefer-coerce-array`: `x ?? {}` becomes
`coerceObject(x)`. The rule flags a `??` whose default is a bare `{}`
literal, so a populated default or an asserted `({} as Foo)` still
passes; a `x ?? y ?? {}` chain maps onto `coerceObject(x, y)`.

Every hit across `lib/` converted cleanly, so no disable comments were
needed.
2026-08-28 09:27:59 +00:00

173 lines
4.4 KiB
TypeScript

import { isNumber } from '@sindresorhus/is';
import JSON5 from 'json5';
import { parse as jsoncWeaverParse } from 'jsonc-weaver';
import type { JsonValue } from 'type-fest';
import { GlobalConfig } from '../config/global.ts';
import { InheritConfig, NOT_PRESENT } from '../config/inherit.ts';
import type { GlobalInheritableConfig } from '../config/types.ts';
import {
AZURE_API_USING_HOST_TYPES,
BITBUCKET_API_USING_HOST_TYPES,
BITBUCKET_SERVER_API_USING_HOST_TYPES,
FORGEJO_API_USING_HOST_TYPES,
GITEA_API_USING_HOST_TYPES,
GITHUB_API_USING_HOST_TYPES,
GITLAB_API_USING_HOST_TYPES,
} from '../constants/index.ts';
import { logger } from '../logger/index.ts';
import type { Nullish } from '../types/index.ts';
import * as hostRules from './host-rules.ts';
import { coerceObject } from './object.ts';
import { parseUrl } from './url.ts';
/**
* Tries to detect the `platform` from a url.
*
* @param url the url to detect `platform` from
* @returns matched `platform` if found, otherwise `null`
*/
export function detectPlatform(
url: string,
):
| 'azure'
| 'bitbucket'
| 'bitbucket-server'
| 'forgejo'
| 'gitea'
| 'github'
| 'gitlab'
| null {
const { hostname } = coerceObject(parseUrl(url));
if (hostname === 'dev.azure.com' || hostname?.endsWith('.visualstudio.com')) {
return 'azure';
}
if (hostname === 'bitbucket.org' || hostname === 'bitbucket.com') {
return 'bitbucket';
}
if (hostname?.includes('bitbucket')) {
return 'bitbucket-server';
}
if (hostname?.includes('forgejo')) {
return 'forgejo';
}
if (hostname && ['codeberg.org', 'codefloe.com'].includes(hostname)) {
return 'forgejo';
}
if (
hostname &&
(['gitea.com'].includes(hostname) || hostname.includes('gitea'))
) {
return 'gitea';
}
if (hostname === 'github.com' || hostname?.includes('github')) {
return 'github';
}
if (hostname === 'gitlab.com' || hostname?.includes('gitlab')) {
return 'gitlab';
}
const hostType = hostRules.hostType({ url });
if (!hostType) {
return null;
}
if (AZURE_API_USING_HOST_TYPES.includes(hostType)) {
return 'azure';
}
if (BITBUCKET_SERVER_API_USING_HOST_TYPES.includes(hostType)) {
return 'bitbucket-server';
}
if (BITBUCKET_API_USING_HOST_TYPES.includes(hostType)) {
return 'bitbucket';
}
if (FORGEJO_API_USING_HOST_TYPES.includes(hostType)) {
return 'forgejo';
}
if (GITEA_API_USING_HOST_TYPES.includes(hostType)) {
return 'gitea';
}
if (GITHUB_API_USING_HOST_TYPES.includes(hostType)) {
return 'github';
}
if (GITLAB_API_USING_HOST_TYPES.includes(hostType)) {
return 'gitlab';
}
return null;
}
export function noLeadingAtSymbol(input: string): string {
return input.startsWith('@') ? input.slice(1) : input;
}
export function parseJson(
content: Nullish<string>,
filename: string,
): JsonValue {
if (!content) {
return null;
}
if (filename.endsWith('.jsonc')) {
return parseJsonc(content);
}
if (filename.endsWith('.json5')) {
return JSON5.parse(content);
}
return parseJsonWithFallback(content, filename);
}
export function parseJsonWithFallback(
content: string,
context: string,
): JsonValue {
let parsedJson: JsonValue;
try {
parsedJson = parseJsonc(content);
} catch {
// warn if json5 format used in json
parsedJson = JSON5.parse(content);
logger.warn(
{ context },
'File contents are invalid JSONC but parse using JSON5. Support for this will be removed in a future release so please change to a support .json5 file name or ensure correct JSON syntax.',
);
}
return parsedJson;
}
export function parseJsonc(content: string): JsonValue {
return jsoncWeaverParse(content);
}
/**
* Use only if an option is inherited + globalOnly
* For globalOnly options use GlobalConfig.get
*/
export function getInheritedOrGlobal<Key extends keyof GlobalInheritableConfig>(
key: Key,
): GlobalInheritableConfig[Key] {
const inheritedValue = InheritConfig.get(key);
const globalValue = GlobalConfig.get(key);
if (inheritedValue !== NOT_PRESENT) {
// Don't allow inherited config to make `onboardingAutoCloseAge` a higher value than our global setting
if (
key === 'onboardingAutoCloseAge' &&
isNumber(inheritedValue) &&
isNumber(globalValue) &&
globalValue < inheritedValue
) {
return globalValue;
}
return inheritedValue;
}
return globalValue;
}