0
0
Fork 0
mirror of https://github.com/renovatebot/renovate.git synced 2025-04-12 20:08:13 +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,
}
`;
const validJsoncString = `
{
// This is a comment
"name": "John Doe",
"age": 30,
"city": "New York"
}
`;
describe('util/common', () => {
beforeEach(() => hostRules.clear());
@ -106,7 +114,7 @@ describe('util/common', () => {
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({
name: 'Bob',
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.',
);
});
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 * as JSONC from 'jsonc-parser';
import type { JsonValue } from 'type-fest';
import {
BITBUCKET_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;
}
export function parseJson(content: string | null, filename: string): unknown {
export function parseJson(content: string | null, filename: string): JsonValue {
if (!content) {
return null;
}
return filename.endsWith('.json5')
? JSON5.parse(content)
: parseJsonWithFallback(content, filename);
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,
): unknown {
let parsedJson: unknown;
): JsonValue {
let parsedJson: JsonValue;
try {
parsedJson = JSON.parse(content);
@ -108,3 +116,12 @@ export function parseJsonWithFallback(
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 * as JSONC from 'jsonc-parser';
import { DateTime } from 'luxon';
import type { JsonArray, JsonValue } from 'type-fest';
import {
@ -11,6 +10,7 @@ import {
} from 'zod';
import { logger } from '../logger';
import type { PackageDependency } from '../modules/manager/types';
import { parseJsonc } from './common';
import { parse as parseToml } from './toml';
import type { YamlOptions } 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 => {
const errors: JSONC.ParseError[] = [];
const value = JSONC.parse(str, errors, { allowTrailingComma: true });
if (errors.length === 0) {
return value;
try {
return parseJsonc(str);
} catch {
ctx.addIssue({ code: 'custom', message: 'Invalid JSONC' });
return z.NEVER;
}
ctx.addIssue({ code: 'custom', message: 'Invalid JSONC' });
return z.NEVER;
});
export const UtcDate = z