0
0
Fork 0
mirror of https://github.com/renovatebot/renovate.git synced 2025-04-17 13:52:40 +00:00

fix(util): parse jsonc as jsonc ()

This commit is contained in:
Risu 2025-04-04 17:23:01 +11:00 committed by GitHub
parent c8baf9a270
commit c1581761cb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 62 additions and 14 deletions

View file

@ -27,6 +27,14 @@ const onlyJson5parsableString = `
"isMarried": false, "isMarried": false,
} }
`; `;
const validJsoncString = `
{
// This is a comment
"name": "John Doe",
"age": 30,
"city": "New York"
}
`;
describe('util/common', () => { describe('util/common', () => {
beforeEach(() => hostRules.clear()); beforeEach(() => hostRules.clear());
@ -106,7 +114,7 @@ describe('util/common', () => {
expect(() => parseJson(invalidJsonString, 'renovate.json')).toThrow(); expect(() => parseJson(invalidJsonString, 'renovate.json')).toThrow();
}); });
it('catches and warns if content parsing faield with JSON.parse but not with JSON5.parse', () => { it('catches and warns if content parsing failed with JSON.parse but not with JSON5.parse', () => {
expect(parseJson(onlyJson5parsableString, 'renovate.json')).toEqual({ expect(parseJson(onlyJson5parsableString, 'renovate.json')).toEqual({
name: 'Bob', name: 'Bob',
age: 35, age: 35,
@ -118,5 +126,29 @@ describe('util/common', () => {
'File contents are invalid JSON 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.', 'File contents are invalid JSON 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.',
); );
}); });
it('does not warn if filename ends with .jsonc', () => {
parseJson(validJsoncString, 'renovate.jsonc');
expect(logger.logger.warn).not.toHaveBeenCalled();
});
it('does not warn if filename ends with .json5', () => {
parseJson(onlyJson5parsableString, 'renovate.json5');
expect(logger.logger.warn).not.toHaveBeenCalled();
});
});
describe('parseJsonc', () => {
it('returns parsed jsonc', () => {
expect(parseJson(validJsoncString, 'renovate.jsonc')).toEqual({
name: 'John Doe',
age: 30,
city: 'New York',
});
});
it('throws error for invalid jsonc', () => {
expect(() => parseJson(invalidJsonString, 'renovate.jsonc')).toThrow();
});
}); });
}); });

View file

@ -1,4 +1,6 @@
import JSON5 from 'json5'; import JSON5 from 'json5';
import * as JSONC from 'jsonc-parser';
import type { JsonValue } from 'type-fest';
import { import {
BITBUCKET_API_USING_HOST_TYPES, BITBUCKET_API_USING_HOST_TYPES,
BITBUCKET_SERVER_API_USING_HOST_TYPES, BITBUCKET_SERVER_API_USING_HOST_TYPES,
@ -80,21 +82,27 @@ export function noLeadingAtSymbol(input: string): string {
return input.startsWith('@') ? input.slice(1) : input; return input.startsWith('@') ? input.slice(1) : input;
} }
export function parseJson(content: string | null, filename: string): unknown { export function parseJson(content: string | null, filename: string): JsonValue {
if (!content) { if (!content) {
return null; return null;
} }
return filename.endsWith('.json5') if (filename.endsWith('.jsonc')) {
? JSON5.parse(content) return parseJsonc(content);
: parseJsonWithFallback(content, filename); }
if (filename.endsWith('.json5')) {
return JSON5.parse(content);
}
return parseJsonWithFallback(content, filename);
} }
export function parseJsonWithFallback( export function parseJsonWithFallback(
content: string, content: string,
context: string, context: string,
): unknown { ): JsonValue {
let parsedJson: unknown; let parsedJson: JsonValue;
try { try {
parsedJson = JSON.parse(content); parsedJson = JSON.parse(content);
@ -108,3 +116,12 @@ export function parseJsonWithFallback(
return parsedJson; return parsedJson;
} }
export function parseJsonc(content: string): JsonValue {
const errors: JSONC.ParseError[] = [];
const value = JSONC.parse(content, errors, { allowTrailingComma: true });
if (errors.length === 0) {
return value;
}
throw new Error('Invalid JSONC');
}

View file

@ -1,5 +1,4 @@
import JSON5 from 'json5'; import JSON5 from 'json5';
import * as JSONC from 'jsonc-parser';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import type { JsonArray, JsonValue } from 'type-fest'; import type { JsonArray, JsonValue } from 'type-fest';
import { import {
@ -11,6 +10,7 @@ import {
} from 'zod'; } from 'zod';
import { logger } from '../logger'; import { logger } from '../logger';
import type { PackageDependency } from '../modules/manager/types'; import type { PackageDependency } from '../modules/manager/types';
import { parseJsonc } from './common';
import { parse as parseToml } from './toml'; import { parse as parseToml } from './toml';
import type { YamlOptions } from './yaml'; import type { YamlOptions } from './yaml';
import { parseSingleYaml, parseYaml } from './yaml'; import { parseSingleYaml, parseYaml } from './yaml';
@ -226,13 +226,12 @@ export const Json5 = z.string().transform((str, ctx): JsonValue => {
}); });
export const Jsonc = z.string().transform((str, ctx): JsonValue => { export const Jsonc = z.string().transform((str, ctx): JsonValue => {
const errors: JSONC.ParseError[] = []; try {
const value = JSONC.parse(str, errors, { allowTrailingComma: true }); return parseJsonc(str);
if (errors.length === 0) { } catch {
return value; ctx.addIssue({ code: 'custom', message: 'Invalid JSONC' });
return z.NEVER;
} }
ctx.addIssue({ code: 'custom', message: 'Invalid JSONC' });
return z.NEVER;
}); });
export const UtcDate = z export const UtcDate = z