From 53263bbfda3eebf20506defd8ae78b9c53048766 Mon Sep 17 00:00:00 2001 From: Sergei Zharinov Date: Tue, 27 Jun 2023 16:09:59 +0300 Subject: [PATCH] feat: Clone objects via wrapper (#22970) --- lib/config/massage.ts | 5 ++- lib/config/presets/index.ts | 3 +- lib/config/utils.ts | 5 ++- lib/logger/utils.ts | 2 +- .../datasource/jenkins-plugins/index.ts | 2 +- lib/modules/manager/bazel-module/rules.ts | 3 +- lib/modules/manager/gradle/parser/common.ts | 3 +- .../update/locked-dependency/index.spec.ts | 3 +- lib/util/__snapshots__/clone.spec.ts.snap | 13 ------ lib/util/clone.spec.ts | 40 +++++++++++++------ lib/util/clone.ts | 1 - .../memory-cache-strategy.spec.ts | 11 ++--- .../package-cache-strategy.spec.ts | 3 +- lib/util/host-rules.ts | 5 ++- lib/workers/repository/init/index.ts | 3 +- .../repository/onboarding/branch/config.ts | 3 +- lib/workers/repository/package-files.ts | 3 +- lib/workers/repository/process/fetch.ts | 3 +- lib/workers/repository/process/index.ts | 3 +- .../process/lookup/filter-checks.spec.ts | 3 +- .../repository/process/lookup/index.ts | 3 +- .../update/pr/changelog/release-notes.spec.ts | 5 ++- 22 files changed, 72 insertions(+), 53 deletions(-) delete mode 100644 lib/util/__snapshots__/clone.spec.ts.snap diff --git a/lib/config/massage.ts b/lib/config/massage.ts index 5130edd2bd5c34..094237c11bfdb2 100644 --- a/lib/config/massage.ts +++ b/lib/config/massage.ts @@ -1,4 +1,5 @@ import is from '@sindresorhus/is'; +import { clone } from '../util/clone'; import { getOptions } from './options'; import type { PackageRule, RenovateConfig, UpdateType } from './types'; @@ -16,7 +17,7 @@ export function massageConfig(config: RenovateConfig): RenovateConfig { } }); } - const massagedConfig = structuredClone(config); + const massagedConfig = clone(config); for (const [key, val] of Object.entries(config)) { if (allowedStrings.includes(key) && is.string(val)) { massagedConfig[key] = [val]; @@ -55,7 +56,7 @@ export function massageConfig(config: RenovateConfig): RenovateConfig { PackageRule ][]) { if (updateTypes.includes(key)) { - let newRule = structuredClone(rule); + let newRule = clone(rule); Object.keys(newRule).forEach((newKey) => { if (!(newKey.startsWith(`match`) || newKey.startsWith('exclude'))) { delete newRule[newKey]; diff --git a/lib/config/presets/index.ts b/lib/config/presets/index.ts index 32d17ea8212751..24459e102cb960 100644 --- a/lib/config/presets/index.ts +++ b/lib/config/presets/index.ts @@ -6,6 +6,7 @@ import { import { logger } from '../../logger'; import { ExternalHostError } from '../../types/errors/external-host-error'; import * as memCache from '../../util/cache/memory'; +import { clone } from '../../util/clone'; import { regEx } from '../../util/regex'; import * as massage from '../massage'; import * as migration from '../migration'; @@ -273,7 +274,7 @@ export async function resolveConfigPresets( _ignorePresets?: string[], existingPresets: string[] = [] ): Promise { - let ignorePresets = structuredClone(_ignorePresets); + let ignorePresets = clone(_ignorePresets); if (!ignorePresets || ignorePresets.length === 0) { ignorePresets = inputConfig.ignorePresets ?? []; } diff --git a/lib/config/utils.ts b/lib/config/utils.ts index 140d5f0df030a6..4d5161e78ef958 100644 --- a/lib/config/utils.ts +++ b/lib/config/utils.ts @@ -1,4 +1,5 @@ import { logger } from '../logger'; +import { clone } from '../util/clone'; import { getHighestVulnerabilitySeverity } from '../util/vulnerability/utils'; import * as options from './options'; import type { RenovateConfig } from './types'; @@ -11,8 +12,8 @@ export function mergeChildConfig< if (!child) { return parent as never; } - const parentConfig = structuredClone(parent); - const childConfig = structuredClone(child); + const parentConfig = clone(parent); + const childConfig = clone(child); const config: Record = { ...parentConfig, ...childConfig }; // Ensure highest severity survives parent / child merge diff --git a/lib/logger/utils.ts b/lib/logger/utils.ts index 84144229650e37..46ac17e604be07 100644 --- a/lib/logger/utils.ts +++ b/lib/logger/utils.ts @@ -149,7 +149,7 @@ export default function prepareError(err: Error): Record { statusCode: err.response?.statusCode, statusMessage: err.response?.statusMessage, body: - // istanbul ignore if: not easily testable + // istanbul ignore next: not easily testable err.name === 'TimeoutError' ? undefined : structuredClone(err.response.body), diff --git a/lib/modules/datasource/jenkins-plugins/index.ts b/lib/modules/datasource/jenkins-plugins/index.ts index c0cb43fdd32268..09b0d4819942de 100644 --- a/lib/modules/datasource/jenkins-plugins/index.ts +++ b/lib/modules/datasource/jenkins-plugins/index.ts @@ -33,7 +33,7 @@ export class JenkinsPluginsDatasource extends Datasource { return null; } - const result = structuredClone(plugin); + const result = clone(plugin); const versions = await this.getJenkinsPluginVersions(); const releases = versions[packageName]; result.releases = releases ? clone(releases) : []; diff --git a/lib/modules/manager/bazel-module/rules.ts b/lib/modules/manager/bazel-module/rules.ts index 64eec54890dd9b..173104facc7ce9 100644 --- a/lib/modules/manager/bazel-module/rules.ts +++ b/lib/modules/manager/bazel-module/rules.ts @@ -3,6 +3,7 @@ import parseGithubUrl from 'github-url-from-git'; import { z } from 'zod'; import { logger } from '../../../logger'; import type { SkipReason } from '../../../types'; +import { clone } from '../../../util/clone'; import { regEx } from '../../../util/regex'; import { BazelDatasource } from '../../datasource/bazel'; import { GithubTagsDatasource } from '../../datasource/github-tags'; @@ -50,7 +51,7 @@ function isMerge(value: BazelModulePackageDep): value is MergePackageDep { export function bazelModulePackageDepToPackageDependency( bmpd: BazelModulePackageDep ): PackageDependency { - const copy: BazelModulePackageDep = structuredClone(bmpd); + const copy: BazelModulePackageDep = clone(bmpd); if (isOverride(copy)) { const partial = copy as Partial; delete partial.bazelDepSkipReason; diff --git a/lib/modules/manager/gradle/parser/common.ts b/lib/modules/manager/gradle/parser/common.ts index 6f92589544e793..a57da4f9fddbdb 100644 --- a/lib/modules/manager/gradle/parser/common.ts +++ b/lib/modules/manager/gradle/parser/common.ts @@ -1,4 +1,5 @@ import { lexer, parser, query as q } from 'good-enough-parser'; +import { clone } from '../../../../util/clone'; import { regEx } from '../../../../util/regex'; import type { Ctx, @@ -51,7 +52,7 @@ export function reduceNestingDepth(ctx: Ctx): Ctx { } export function prependNestingDepth(ctx: Ctx): Ctx { - ctx.varTokens = [...structuredClone(ctx.tmpNestingDepth), ...ctx.varTokens]; + ctx.varTokens = [...clone(ctx.tmpNestingDepth), ...ctx.varTokens]; return ctx; } diff --git a/lib/modules/manager/npm/update/locked-dependency/index.spec.ts b/lib/modules/manager/npm/update/locked-dependency/index.spec.ts index 930836f4ff0de9..857621abaaf9a0 100644 --- a/lib/modules/manager/npm/update/locked-dependency/index.spec.ts +++ b/lib/modules/manager/npm/update/locked-dependency/index.spec.ts @@ -1,6 +1,7 @@ import { updateLockedDependency } from '../..'; import { Fixtures } from '../../../../../../test/fixtures'; import * as httpMock from '../../../../../../test/http-mock'; +import { clone } from '../../../../../util/clone'; import type { UpdateLockedConfig } from '../../../types'; const packageFileContent = Fixtures.get('package.json', './package-lock'); @@ -113,7 +114,7 @@ describe('modules/manager/npm/update/locked-dependency/index', () => { }); it('fails to remediate if parent dep cannot support', async () => { - const acceptsModified = structuredClone(acceptsJson); + const acceptsModified = clone(acceptsJson); acceptsModified.versions['2.0.0'] = {}; httpMock .scope('https://registry.npmjs.org') diff --git a/lib/util/__snapshots__/clone.spec.ts.snap b/lib/util/__snapshots__/clone.spec.ts.snap deleted file mode 100644 index 494dc851637c13..00000000000000 --- a/lib/util/__snapshots__/clone.spec.ts.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`util/clone maintains same order: { - name: 'object', - type: 'object', - isObject: true, - } 1`] = ` -{ - "isObject": true, - "name": "object", - "type": "object", -} -`; diff --git a/lib/util/clone.spec.ts b/lib/util/clone.spec.ts index dc03323d6690cc..f585b3c1b9555b 100644 --- a/lib/util/clone.spec.ts +++ b/lib/util/clone.spec.ts @@ -1,25 +1,41 @@ import { clone } from './clone'; describe('util/clone', () => { - it('returns null', () => { - const res = clone(null); - expect(res).toBeNull(); + test.each` + input | expected + ${undefined} | ${undefined} + ${null} | ${null} + ${true} | ${true} + ${false} | ${false} + ${0} | ${0} + ${1} | ${1} + ${NaN} | ${NaN} + ${Infinity} | ${Infinity} + ${-Infinity} | ${-Infinity} + ${''} | ${''} + ${'string'} | ${'string'} + ${[]} | ${[]} + ${[1, 2, 3]} | ${[1, 2, 3]} + ${{}} | ${{}} + ${{ a: 1 }} | ${{ a: 1 }} + `('returns $expected when input is $input', ({ input, expected }) => { + const res = clone(input); + expect(res).toStrictEqual(expected); }); it('maintains same order', () => { const obj: any = { - name: 'object', - type: 'object', - isObject: true, + b: 'foo', + a: 'bar', + c: 'baz', }; const res = clone(obj); - - expect(res).toMatchSnapshot(`{ - name: 'object', - type: 'object', - isObject: true, - }`); + expect(Object.entries(res)).toMatchObject([ + ['b', 'foo'], + ['a', 'bar'], + ['c', 'baz'], + ]); }); it('assigns "[Circular]" to circular references', () => { diff --git a/lib/util/clone.ts b/lib/util/clone.ts index 93a81cdcb1b075..fca0b85c3cea38 100644 --- a/lib/util/clone.ts +++ b/lib/util/clone.ts @@ -4,7 +4,6 @@ import { quickStringify } from './stringify'; /** * Creates a deep clone of an object. - * @deprecated Use {@link structuredClone} instead. * @param input The object to clone. */ export function clone(input: T): T { diff --git a/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts b/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts index 71ab7203107807..88849193a12b66 100644 --- a/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts +++ b/lib/util/github/graphql/cache-strategies/memory-cache-strategy.spec.ts @@ -1,5 +1,6 @@ import { DateTime, Settings } from 'luxon'; import * as memCache from '../../../cache/memory'; +import { clone } from '../../../clone'; import type { GithubDatasourceItem, GithubGraphqlCacheRecord } from '../types'; import { GithubGraphqlMemoryCacheStrategy } from './memory-cache-strategy'; @@ -44,7 +45,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items, createdAt: isoTs('2022-10-01 15:30'), }; - memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); // At this moment, cache is valid let now = '2022-10-31 15:29:59'; @@ -84,7 +85,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items: oldItems, createdAt: isoTs('2022-10-30 12:00'), }; - memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); const now = '2022-10-31 15:30'; mockTime(now); @@ -120,7 +121,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items: oldItems, createdAt: isoTs('2022-10-30 12:00'), }; - memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); const now = '2022-10-31 15:30'; mockTime(now); @@ -146,7 +147,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items: oldItems, createdAt: isoTs('2022-12-31 12:00'), }; - memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); const now = '2022-12-31 23:59'; mockTime(now); @@ -190,7 +191,7 @@ describe('util/github/graphql/cache-strategies/memory-cache-strategy', () => { items, createdAt: isoTs('2022-10-30 12:00'), }; - memCache.set('github-graphql-cache:foo:bar', structuredClone(cacheRecord)); + memCache.set('github-graphql-cache:foo:bar', clone(cacheRecord)); const now = '2022-10-31 15:30'; mockTime(now); diff --git a/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts b/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts index b288f2eeb6d139..80c02add16a5b3 100644 --- a/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts +++ b/lib/util/github/graphql/cache-strategies/package-cache-strategy.spec.ts @@ -1,5 +1,6 @@ import { DateTime, Settings } from 'luxon'; import * as packageCache from '../../../cache/package'; +import { clone } from '../../../clone'; import type { GithubDatasourceItem, GithubGraphqlCacheRecord } from '../types'; import { GithubGraphqlPackageCacheStrategy } from './package-cache-strategy'; @@ -30,7 +31,7 @@ describe('util/github/graphql/cache-strategies/package-cache-strategy', () => { items: oldItems, createdAt: isoTs('2022-10-15 12:00'), }; - cacheGet.mockResolvedValueOnce(structuredClone(cacheRecord)); + cacheGet.mockResolvedValueOnce(clone(cacheRecord)); const now = '2022-10-30 12:00'; mockTime(now); diff --git a/lib/util/host-rules.ts b/lib/util/host-rules.ts index 176d3e283dc18a..536f01ba51f18c 100644 --- a/lib/util/host-rules.ts +++ b/lib/util/host-rules.ts @@ -2,6 +2,7 @@ import is from '@sindresorhus/is'; import merge from 'deepmerge'; import { logger } from '../logger'; import type { HostRule, HostRuleSearchResult } from '../types'; +import { clone } from './clone'; import * as sanitize from './sanitize'; import { toBase64 } from './string'; import { parseUrl, validateUrl } from './url'; @@ -17,7 +18,7 @@ export interface LegacyHostRule { } export function migrateRule(rule: LegacyHostRule & HostRule): HostRule { - const cloned: LegacyHostRule & HostRule = structuredClone(rule); + const cloned: LegacyHostRule & HostRule = clone(rule); delete cloned.hostName; delete cloned.domainName; delete cloned.baseUrl; @@ -188,7 +189,7 @@ export function findAll({ hostType }: { hostType: string }): HostRule[] { * @returns a deep copy of all known host rules without any filtering */ export function getAll(): HostRule[] { - return structuredClone(hostRules); + return clone(hostRules); } export function clear(): void { diff --git a/lib/workers/repository/init/index.ts b/lib/workers/repository/init/index.ts index 10eb930580fd6f..8f5754b44b8078 100644 --- a/lib/workers/repository/init/index.ts +++ b/lib/workers/repository/init/index.ts @@ -3,6 +3,7 @@ import { applySecretsToConfig } from '../../../config/secrets'; import type { RenovateConfig } from '../../../config/types'; import { logger } from '../../../logger'; import { platform } from '../../../modules/platform'; +import { clone } from '../../../util/clone'; import { cloneSubmodules, setUserRepoConfig } from '../../../util/git'; import { getAll } from '../../../util/host-rules'; import { checkIfConfigured } from '../configured'; @@ -14,7 +15,7 @@ import { detectVulnerabilityAlerts } from './vulnerability'; function initializeConfig(config: RenovateConfig): RenovateConfig { return { - ...structuredClone(config), + ...clone(config), errors: [], warnings: [], branchList: [], diff --git a/lib/workers/repository/onboarding/branch/config.ts b/lib/workers/repository/onboarding/branch/config.ts index fdceac6ba71f82..18f42ca5817afa 100644 --- a/lib/workers/repository/onboarding/branch/config.ts +++ b/lib/workers/repository/onboarding/branch/config.ts @@ -6,12 +6,13 @@ import type { RenovateSharedConfig, } from '../../../../config/types'; import { logger } from '../../../../logger'; +import { clone } from '../../../../util/clone'; import { EditorConfig, JSONWriter } from '../../../../util/json-writer'; async function getOnboardingConfig( config: RenovateConfig ): Promise { - let onboardingConfig = structuredClone(config.onboardingConfig); + let onboardingConfig = clone(config.onboardingConfig); let orgPreset: string | undefined; diff --git a/lib/workers/repository/package-files.ts b/lib/workers/repository/package-files.ts index b33bec994c6d6f..02887c20c9ca2b 100644 --- a/lib/workers/repository/package-files.ts +++ b/lib/workers/repository/package-files.ts @@ -1,6 +1,7 @@ import is from '@sindresorhus/is'; import { logger } from '../../logger'; import type { PackageFile } from '../../modules/manager/types'; +import { clone } from '../../util/clone'; export class PackageFiles { private static data = new Map | null>(); @@ -44,7 +45,7 @@ export class PackageFiles { let removed = false; let truncated = false; - const data = new Map(structuredClone(Array.from(this.data))); + const data = new Map(clone(Array.from(this.data))); // filter all deps with skip reason for (const managers of [...data.values()].filter(is.truthy)) { diff --git a/lib/workers/repository/process/fetch.ts b/lib/workers/repository/process/fetch.ts index fc55431d29ba75..e8208445af4d36 100644 --- a/lib/workers/repository/process/fetch.ts +++ b/lib/workers/repository/process/fetch.ts @@ -14,6 +14,7 @@ import type { import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as memCache from '../../../util/cache/memory'; import type { LookupStats } from '../../../util/cache/memory/types'; +import { clone } from '../../../util/clone'; import { applyPackageRules } from '../../../util/package-rules'; import * as p from '../../../util/promises'; import { PackageFiles } from '../package-files'; @@ -37,7 +38,7 @@ async function fetchDepUpdates( packageFileConfig: RenovateConfig & PackageFile, indep: PackageDependency ): Promise { - const dep = structuredClone(indep); + const dep = clone(indep); dep.updates = []; if (is.string(dep.depName)) { dep.depName = dep.depName.trim(); diff --git a/lib/workers/repository/process/index.ts b/lib/workers/repository/process/index.ts index 2386ab5a81b9f2..cf4d87c7f67725 100644 --- a/lib/workers/repository/process/index.ts +++ b/lib/workers/repository/process/index.ts @@ -9,6 +9,7 @@ import type { PackageFile } from '../../../modules/manager/types'; import { platform } from '../../../modules/platform'; import { scm } from '../../../modules/platform/scm'; import { getCache } from '../../../util/cache/repository'; +import { clone } from '../../../util/clone'; import { getBranchList } from '../../../util/git'; import { configRegexPredicate } from '../../../util/regex'; import { addSplit } from '../../../util/split'; @@ -23,7 +24,7 @@ async function getBaseBranchConfig( ): Promise { logger.debug(`baseBranch: ${baseBranch}`); - let baseBranchConfig: RenovateConfig = structuredClone(config); + let baseBranchConfig: RenovateConfig = clone(config); if ( config.useBaseBranchConfig === 'merge' && diff --git a/lib/workers/repository/process/lookup/filter-checks.spec.ts b/lib/workers/repository/process/lookup/filter-checks.spec.ts index c57df340d05b0c..4541bfe3c760f5 100644 --- a/lib/workers/repository/process/lookup/filter-checks.spec.ts +++ b/lib/workers/repository/process/lookup/filter-checks.spec.ts @@ -1,6 +1,7 @@ import { mocked } from '../../../../../test/util'; import type { Release } from '../../../../modules/datasource'; import * as allVersioning from '../../../../modules/versioning'; +import { clone } from '../../../../util/clone'; import * as _dateUtil from '../../../../util/date'; import * as _mergeConfidence from '../../../../util/merge-confidence'; import { toMs } from '../../../../util/pretty-time'; @@ -41,7 +42,7 @@ describe('workers/repository/process/lookup/filter-checks', () => { beforeEach(() => { config = { currentVersion: '1.0.0' }; - sortedReleases = structuredClone(releases); + sortedReleases = clone(releases); jest.resetAllMocks(); dateUtil.getElapsedMs.mockReturnValueOnce(toMs('3 days') ?? 0); dateUtil.getElapsedMs.mockReturnValueOnce(toMs('5 days') ?? 0); diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index f180e0c6b474e2..31da5a9ea71829 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -16,6 +16,7 @@ import { import { getRangeStrategy } from '../../../../modules/manager'; import * as allVersioning from '../../../../modules/versioning'; import { ExternalHostError } from '../../../../types/errors/external-host-error'; +import { clone } from '../../../../util/clone'; import { applyPackageRules } from '../../../../util/package-rules'; import { regEx } from '../../../../util/regex'; import { getBucket } from './bucket'; @@ -82,7 +83,7 @@ export async function lookupUpdates( return res; } - dependency = structuredClone(await getPkgReleases(config)); + dependency = clone(await getPkgReleases(config)); if (!dependency) { // If dependency lookup fails then warn and return const warning: ValidationMessage = { diff --git a/lib/workers/repository/update/pr/changelog/release-notes.spec.ts b/lib/workers/repository/update/pr/changelog/release-notes.spec.ts index 20f4492537e148..7f3071e55919f2 100644 --- a/lib/workers/repository/update/pr/changelog/release-notes.spec.ts +++ b/lib/workers/repository/update/pr/changelog/release-notes.spec.ts @@ -2,6 +2,7 @@ import { DateTime } from 'luxon'; import { Fixtures } from '../../../../../../test/fixtures'; import * as httpMock from '../../../../../../test/http-mock'; import { mocked, partial } from '../../../../../../test/util'; +import { clone } from '../../../../../util/clone'; import * as githubGraphql from '../../../../../util/github/graphql'; import type { GithubReleaseItem } from '../../../../../util/github/graphql/types'; import * as _hostRules from '../../../../../util/host-rules'; @@ -1134,7 +1135,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { it('handles github sourceDirectory', async () => { const sourceDirectory = 'packages/foo'; - const subdirTree = structuredClone(githubTreeResponse); + const subdirTree = clone(githubTreeResponse); for (const file of subdirTree.tree) { file.path = `${sourceDirectory}/${file.path}`; } @@ -1305,7 +1306,7 @@ describe('workers/repository/update/pr/changelog/release-notes', () => { it('handles gitlab sourceDirectory', async () => { const sourceDirectory = 'packages/foo'; - const response = structuredClone(gitlabTreeResponse).map((file) => ({ + const response = clone(gitlabTreeResponse).map((file) => ({ ...file, path: `${sourceDirectory}/${file.path}`, }));