Files
Sebastian Poxhofer 16ef1715e9 refactor(linters): add rules for @sindresorhus/is helpers (#45229)
* feat(lint): add renovate/prefer-is-helpers and renovate/prefer-is-object rules

Flag ad-hoc type checks that have @sindresorhus/is equivalents:
- .filter(Boolean) -> .filter(isTruthy)
- typeof x === 'string' -> isString(x)
- x === null || x === undefined -> isNullOrUndefined(x) (and negation)

The typeof x === 'object' shape lives in a separate suggestion-only rule
(renovate/prefer-is-object, kept at warn) because isObject()/isPlainObject()
are not drop-in equivalents: null, arrays and functions behave differently.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: fix renovate/prefer-is-helpers violations

Replace ad-hoc type checks with @sindresorhus/is helpers across lib/:
- 13x .filter(Boolean) -> .filter(isTruthy)
- 36x typeof x ==/!== 'string' -> isString(x) / !isString(x)
- 6x x === null || x === undefined -> isNullOrUndefined(x) (and negation)

Promote renovate/prefer-is-helpers to error now that the codebase is clean.
renovate/prefer-is-object stays at warn: its 15 remaining sites guard
parsed YAML/JSON or unknown values where isObject()/isPlainObject() are
not provably equivalent (null/array/function semantics).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor: enforce renovate/prefer-is-object at error level

Resolve the 15 remaining warnings and raise the rule from warn to error:

- Migrate 10 sites to isObject() where the guarded value provably cannot
  be a function (parsed YAML/JSON, typed exec command arguments, or
  test-controlled fixtures): helm-values, woodpecker, bun, pre-commit
  managers, exec utils, file package cache, and the github http spec.
  In the bun manager this also fixes a latent crash where a null
  `workspaces` value would throw on the `in` operator.
- Keep 5 sites as reasoned oxlint-disable comments where excluding
  functions is load-bearing: the byte-exact fingerprint canonicalizer
  (2), the circularity walker, the zod result guard, and the generic
  hasKey helper.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-13 14:22:03 +00:00

108 lines
3.3 KiB
TypeScript

import { isObject } from '@sindresorhus/is';
import { logger } from '../../../logger/index.ts';
import { parseYaml } from '../../../util/yaml.ts';
import { id as dockerVersioning } from '../../versioning/docker/index.ts';
import { getDep } from '../dockerfile/extract.ts';
import type {
ExtractConfig,
PackageDependency,
PackageFileContent,
} from '../types.ts';
import type { HelmDockerImageDependency } from './types.ts';
import {
matchesHelmValuesDockerHeuristic,
matchesHelmValuesInlineImage,
} from './util.ts';
function getHelmDep(
registry: string,
repository: string,
tag: string,
registryAliases: Record<string, string> | undefined,
): PackageDependency {
const dep = getDep(`${registry}${repository}:${tag}`, false, registryAliases);
dep.replaceString = tag;
dep.versioning = dockerVersioning;
dep.autoReplaceStringTemplate =
'{{newValue}}{{#if newDigest}}@{{newDigest}}{{/if}}';
return dep;
}
/**
* Recursively find all supported dependencies in the yaml object.
*
* @param parsedContent
*/
export function findDependencies(
parsedContent: Record<string, unknown> | HelmDockerImageDependency,
registryAliases: Record<string, string> | undefined,
): PackageDependency[] {
return findDependenciesInternal(parsedContent, [], registryAliases);
}
export function findDependenciesInternal(
parsedContent: Record<string, unknown> | HelmDockerImageDependency,
packageDependencies: PackageDependency[],
registryAliases: Record<string, string> | undefined,
): PackageDependency[] {
if (!isObject(parsedContent)) {
return packageDependencies;
}
Object.entries(parsedContent).forEach(([key, value]) => {
if (matchesHelmValuesDockerHeuristic(key, value)) {
const currentItem = value;
let registry = currentItem.registry;
registry = registry ? `${registry}/` : '';
const repository = String(currentItem.repository);
const tag = `${currentItem.tag ?? currentItem.version}`;
packageDependencies.push(
getHelmDep(registry, repository, tag, registryAliases),
);
} else if (matchesHelmValuesInlineImage(key, value)) {
packageDependencies.push(getDep(value, true, registryAliases));
} else {
findDependenciesInternal(
value as Record<string, unknown>,
packageDependencies,
registryAliases,
);
}
});
return packageDependencies;
}
export function extractPackageFile(
content: string,
packageFile: string,
config: ExtractConfig,
): PackageFileContent | null {
let parsedContent: Record<string, unknown>[] | HelmDockerImageDependency[];
try {
// a parser that allows extracting line numbers would be preferable, with
// the current approach we need to match anything we find again during the update
// TODO: fix me (#9610)
parsedContent = parseYaml(content) as any;
} catch (err) {
logger.debug({ err, packageFile }, 'Failed to parse helm-values YAML');
return null;
}
try {
const deps: PackageDependency[] = [];
for (const con of parsedContent) {
deps.push(...findDependencies(con, config.registryAliases));
}
if (deps.length) {
return { deps };
}
} catch (err) /* istanbul ignore next */ {
logger.debug(
{ err, packageFile },
'Error parsing helm-values parsed content',
);
}
return null;
}