Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(config-migration): invoke applyPrettierFormatting at the commit stage #18150

Merged
merged 20 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ba8b19c
move applyPrettierFormatting to the commiting phase
Gabriel-Ladzaretti Oct 5, 2022
9f7a640
apply code review suggestions
Gabriel-Ladzaretti Oct 6, 2022
e66019b
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Oct 7, 2022
5c371c8
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Oct 9, 2022
5d543aa
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Oct 11, 2022
2fc0144
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Oct 14, 2022
2a3348a
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Oct 15, 2022
64583a1
extract applyPrettier to the global scope
Gabriel-Ladzaretti Oct 15, 2022
04cbc97
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Oct 16, 2022
05c724e
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Oct 23, 2022
99c92a9
add wrapper function for applyPrettierFormatting
Gabriel-Ladzaretti Oct 23, 2022
862d9de
update migrated-data.spec.ts
Gabriel-Ladzaretti Oct 25, 2022
f5945c4
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Oct 29, 2022
ef865fa
add comments to and rename stripWhitespaces helper func
Gabriel-Ladzaretti Nov 2, 2022
42990de
revert getAsync rename
Gabriel-Ladzaretti Nov 8, 2022
3069291
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Nov 8, 2022
d7c3bcb
Merge branch 'main' into migration-prettier
rarkins Nov 8, 2022
0837ec2
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Nov 13, 2022
90cab25
Merge branch 'main' into migration-prettier
Gabriel-Ladzaretti Nov 15, 2022
3f93edf
Merge branch 'main' into migration-prettier
rarkins Nov 16, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"content": "{\n \"extends\": [\n \":separateMajorReleases\",\n \":prImmediately\",\n \":renovatePrefix\",\n \":semanticPrefixFixDepsChoreOthers\",\n \":updateNotScheduled\",\n \":automergeDisabled\",\n \":maintainLockFilesDisabled\",\n \":autodetectPinVersions\",\n \"group:monorepos\"\n ],\n \"onboarding\": false,\n \"rangeStrategy\": \"replace\",\n \"semanticCommits\": \"enabled\",\n \"timezone\": \"US/Central\",\n \"baseBranches\": [\n \"main\"\n ]\n}\n",
"filename": "renovate.json",
"content": "{\n \"extends\": [\n \":separateMajorReleases\",\n \":prImmediately\",\n \":renovatePrefix\",\n \":semanticPrefixFixDepsChoreOthers\",\n \":updateNotScheduled\",\n \":automergeDisabled\",\n \":maintainLockFilesDisabled\",\n \":autodetectPinVersions\",\n \"group:monorepos\"\n ],\n \"onboarding\": false,\n \"rangeStrategy\": \"replace\",\n \"semanticCommits\": \"enabled\",\n \"timezone\": \"US/Central\",\n \"baseBranches\": [\n \"main\"\n ]\n}\n"
"indent": {
"amount": 2,
"indent": " ",
"type": "space"
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"content": "{\n extends: [\n ':separateMajorReleases',\n ':prImmediately',\n ':renovatePrefix',\n ':semanticPrefixFixDepsChoreOthers',\n ':updateNotScheduled',\n ':automergeDisabled',\n ':maintainLockFilesDisabled',\n ':autodetectPinVersions',\n 'group:monorepos',\n ],\n onboarding: false,\n rangeStrategy: 'replace',\n semanticCommits: 'enabled',\n timezone: 'US/Central',\n baseBranches: [\n 'main',\n ],\n}\n",
"filename": "renovate.json5",
"content": "{\n extends: [\n ':separateMajorReleases',\n ':prImmediately',\n ':renovatePrefix',\n ':semanticPrefixFixDepsChoreOthers',\n ':updateNotScheduled',\n ':automergeDisabled',\n ':maintainLockFilesDisabled',\n ':autodetectPinVersions',\n 'group:monorepos',\n ],\n onboarding: false,\n rangeStrategy: 'replace',\n semanticCommits: 'enabled',\n timezone: 'US/Central',\n baseBranches: [\n 'main',\n ],\n}\n"
"indent": {
"amount": 2,
"indent": " ",
"type": "space"
}
}
20 changes: 18 additions & 2 deletions lib/workers/repository/config-migration/branch/create.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import type { Indent } from 'detect-indent';
import { Fixtures } from '../../../../../test/fixtures';
import { RenovateConfig, getConfig, platform } from '../../../../../test/util';
import {
RenovateConfig,
getConfig,
partial,
platform,
} from '../../../../../test/util';
import { checkoutBranch, commitFiles } from '../../../../util/git';
import { createConfigMigrationBranch } from './create';
import { MigratedDataFactory } from './migrated-data';
import type { MigratedData } from './migrated-data';

jest.mock('../../../../util/git');
Expand All @@ -11,6 +18,10 @@ describe('workers/repository/config-migration/branch/create', () => {
const indent = ' ';
const renovateConfig = JSON.stringify(raw, undefined, indent) + '\n';
const filename = 'renovate.json';
const prettierSpy = jest.spyOn(
MigratedDataFactory,
'applyPrettierFormatting'
);

let config: RenovateConfig;
let migratedConfigData: MigratedData;
Expand All @@ -19,7 +30,12 @@ describe('workers/repository/config-migration/branch/create', () => {
config = getConfig();
config.baseBranch = 'dev';
config.defaultBranch = 'master';
migratedConfigData = { content: renovateConfig, filename };
migratedConfigData = {
content: renovateConfig,
filename,
indent: partial<Indent>({}),
};
prettierSpy.mockResolvedValueOnce(migratedConfigData.content);
});

describe('createConfigMigrationBranch', () => {
Expand Down
5 changes: 4 additions & 1 deletion lib/workers/repository/config-migration/branch/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { commitAndPush } from '../../../../modules/platform/commit';
import { checkoutBranch } from '../../../../util/git';
import { getMigrationBranchName } from '../common';
import { ConfigMigrationCommitMessageFactory } from './commit-message';
import { MigratedDataFactory } from './migrated-data';
import type { MigratedData } from './migrated-data';

export async function createConfigMigrationBranch(
config: Partial<RenovateConfig>,
migratedConfigData: MigratedData
): Promise<string | null> {
logger.debug('createConfigMigrationBranch()');
const contents = migratedConfigData.content;
const configFileName = migratedConfigData.filename;
logger.debug('Creating config migration branch');

Expand All @@ -30,6 +30,9 @@ export async function createConfigMigrationBranch(
}

await checkoutBranch(config.defaultBranch!);
const contents = await MigratedDataFactory.applyPrettierFormatting(
migratedConfigData
);
return commitAndPush({
branchName: getMigrationBranchName(config),
files: [
Expand Down
102 changes: 65 additions & 37 deletions lib/workers/repository/config-migration/branch/migrated-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { logger } from '../../../../logger';
import { readLocalFile } from '../../../../util/fs';
import { getFileList } from '../../../../util/git';
import { detectRepoFileConfig } from '../../init/merge';
import { MigratedDataFactory, applyPrettierFormatting } from './migrated-data';
import { MigratedDataFactory } from './migrated-data';

jest.mock('../../../../config/migration');
jest.mock('../../../../util/git');
Expand Down Expand Up @@ -41,7 +41,6 @@ describe('workers/repository/config-migration/branch/migrated-data', () => {
isMigrated: true,
migratedConfig: migratedConfigObj,
});
mockedFunction(getFileList).mockResolvedValue([]);
});

it('Calls getAsync a first when migration not needed', async () => {
Expand Down Expand Up @@ -86,16 +85,18 @@ describe('workers/repository/config-migration/branch/migrated-data', () => {
});

it('Resets the factory and gets a new value with default indentation', async () => {
mockedFunction(detectIndent).mockReturnValueOnce({
const indent = {
type: undefined,
amount: 0,
// TODO: incompatible types (#7154)
indent: null as never,
});
};
mockedFunction(detectIndent).mockReturnValueOnce(indent);
MigratedDataFactory.reset();
await expect(MigratedDataFactory.getAsync()).resolves.toEqual(
migratedData
);
await expect(MigratedDataFactory.getAsync()).resolves.toEqual({
...migratedData,
indent,
});
});

it('Migrate a JSON5 config file', async () => {
Expand All @@ -119,49 +120,76 @@ describe('workers/repository/config-migration/branch/migrated-data', () => {
'MigratedDataFactory.getAsync() Error initializing renovate MigratedData'
);
});
});

it('format and migrate a JSON config file', async () => {
describe('MigratedDataFactory.applyPrettierFormatting', () => {
beforeAll(() => {
mockedFunction(detectIndent).mockReturnValueOnce({
type: 'space',
amount: 2,
indent: ' ',
});
mockedFunction(detectRepoFileConfig).mockResolvedValueOnce({
configFileName: 'renovate.json',
configFileRaw: rawNonMigrated,
});
mockedFunction(migrateConfig).mockReturnValueOnce({
isMigrated: true,
migratedConfig: migratedConfigObj,
});
mockedFunction(getFileList).mockResolvedValue(['.prettierrc']);
MigratedDataFactory.reset();
await expect(MigratedDataFactory.getAsync()).resolves.toEqual(
formattedMigratedData
);
});

it('should not stop run for invalid package.json', async () => {
mockedFunction(detectRepoFileConfig).mockResolvedValueOnce({
configFileName: 'renovate.json',
configFileRaw: 'abci',
});
mockedFunction(readLocalFile).mockResolvedValueOnce(rawNonMigrated);
MigratedDataFactory.reset();
await expect(MigratedDataFactory.getAsync()).resolves.toEqual(
migratedData
);
beforeEach(() => {
mockedFunction(getFileList).mockResolvedValue([]);
});

it('should not stop run for readLocalFile error', async () => {
mockedFunction(detectRepoFileConfig).mockResolvedValueOnce({
configFileName: 'renovate.json',
configFileRaw: 'abci',
});
it('does not format when no prettier config is present', async () => {
const { content: unformatted } = migratedData;
mockedFunction(readLocalFile).mockResolvedValueOnce(null);
await MigratedDataFactory.getAsync();
await expect(
MigratedDataFactory.applyPrettierFormatting(migratedData)
).resolves.toEqual(unformatted);
});

it('does not format when failing to fetch package.json file', async () => {
const { content: unformatted } = migratedData;
mockedFunction(readLocalFile).mockRejectedValueOnce(null);
MigratedDataFactory.reset();
await expect(MigratedDataFactory.getAsync()).resolves.toEqual(
migratedData
);
await MigratedDataFactory.getAsync();
await expect(
MigratedDataFactory.applyPrettierFormatting(migratedData)
).resolves.toEqual(unformatted);
});

it('does not format when there is an invalid package.json file', async () => {
const { content: unformatted } = migratedData;
mockedFunction(readLocalFile).mockResolvedValueOnce('invalid json');
await MigratedDataFactory.getAsync();
await expect(
MigratedDataFactory.applyPrettierFormatting(migratedData)
).resolves.toEqual(unformatted);
});

it('formats when prettier config file is found', async () => {
const formatted = formattedMigratedData.content;
mockedFunction(getFileList).mockResolvedValue(['.prettierrc']);
await MigratedDataFactory.getAsync();
await expect(
MigratedDataFactory.applyPrettierFormatting(migratedData)
).resolves.toEqual(formatted);
});

it('return original content if its invalid', async () => {
it('formats when finds prettier config inside the package.json file', async () => {
const formatted = formattedMigratedData.content;
mockedFunction(detectRepoFileConfig).mockResolvedValueOnce({
configFileName: 'renovate.json',
});
mockedFunction(readLocalFile).mockResolvedValueOnce('{"prettier":{}}');
await MigratedDataFactory.getAsync();
await expect(
applyPrettierFormatting(`{"name":"Rahul"`, 'json', {
indent: ' ',
amount: 2,
})
).resolves.toBe(`{"name":"Rahul"`);
MigratedDataFactory.applyPrettierFormatting(migratedData)
).resolves.toEqual(formatted);
});
});
});
84 changes: 49 additions & 35 deletions lib/workers/repository/config-migration/branch/migrated-data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import detectIndent from 'detect-indent';
import JSON5 from 'json5';
import prettier from 'prettier';
import prettier, { BuiltInParserName } from 'prettier';
import upath from 'upath';
import { migrateConfig } from '../../../../config/migration';
import { logger } from '../../../../logger';
import { readLocalFile } from '../../../../util/fs';
Expand All @@ -10,7 +11,9 @@ import { detectRepoFileConfig } from '../../init/merge';
export interface MigratedData {
content: string;
filename: string;
indent: Indent;
}

interface Indent {
amount: number;
indent: string;
Expand All @@ -30,44 +33,52 @@ const prettierConfigFilenames = new Set([
'.prettierrc.toml',
]);

export type PrettierParser = BuiltInParserName;

export async function applyPrettierFormatting(
content: string,
parser: string,
indent: Indent
parser: PrettierParser,
indent?: Indent
): Promise<string> {
const fileList = await getFileList();
let prettierExists = fileList.some((file) =>
prettierConfigFilenames.has(file)
);
if (!prettierExists) {
try {
const packageJsonContent = await readLocalFile('package.json', 'utf8');
prettierExists =
packageJsonContent && JSON.parse(packageJsonContent).prettier;
} catch {
logger.warn(
'applyPrettierFormatting() - Error processing package.json file'
);
try {
logger.trace('applyPrettierFormatting - START');
const fileList = await getFileList();
let prettierExists = fileList.some((file) =>
prettierConfigFilenames.has(file)
);

if (!prettierExists) {
try {
const packageJsonContent = await readLocalFile('package.json', 'utf8');
prettierExists =
packageJsonContent && JSON.parse(packageJsonContent).prettier;
} catch {
logger.warn(
'applyPrettierFormatting - Error processing package.json file'
);
}
}
}

if (!prettierExists) {
return content;
if (!prettierExists) {
return content;
}
const options = {
parser,
tabWidth: indent?.amount === 0 ? 2 : indent?.amount,
useTabs: indent?.type === 'tab',
};

return prettier.format(content, options);
} finally {
logger.trace('applyPrettierFormatting - END');
}
const options = {
parser,
tabWidth: indent.amount === 0 ? 2 : indent.amount,
useTabs: indent.type === 'tab',
};

return prettier.format(content, options);
}

export class MigratedDataFactory {
// singleton
private static data: MigratedData | null;

public static async getAsync(): Promise<MigratedData | null> {
static async getAsync(): Promise<MigratedData | null> {
if (this.data) {
return this.data;
}
Expand All @@ -81,10 +92,19 @@ export class MigratedDataFactory {
return this.data;
}

public static reset(): void {
static reset(): void {
this.data = null;
}

static applyPrettierFormatting({
content,
filename,
indent,
}: MigratedData): Promise<string> {
const parser = upath.extname(filename).replace('.', '') as PrettierParser;
return applyPrettierFormatting(content, parser, indent);
}

private static async build(): Promise<MigratedData | null> {
let res: MigratedData | null = null;
try {
Expand Down Expand Up @@ -116,17 +136,11 @@ export class MigratedDataFactory {
content = JSON.stringify(migratedConfig, undefined, indentSpace);
}

// format if prettier is found in the user's repo
content = await applyPrettierFormatting(
content,
filename.endsWith('.json5') ? 'json5' : 'json',
indent
);
if (!content.endsWith('\n')) {
content += '\n';
}

res = { content, filename };
res = { content, filename, indent };
} catch (err) {
logger.debug(
{ err },
Expand Down
Loading