diff --git a/lib/util/common.spec.ts b/lib/util/common.spec.ts index 7c0d6901ab..d9049a675f 100644 --- a/lib/util/common.spec.ts +++ b/lib/util/common.spec.ts @@ -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(); + }); }); }); diff --git a/lib/util/common.ts b/lib/util/common.ts index 07613a060c..2ddf510b80 100644 --- a/lib/util/common.ts +++ b/lib/util/common.ts @@ -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'); +} diff --git a/lib/util/schema-utils.ts b/lib/util/schema-utils.ts index fa408081c9..e5be5f23ba 100644 --- a/lib/util/schema-utils.ts +++ b/lib/util/schema-utils.ts @@ -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