diff --git a/common/changes/@rushstack/localization-utilities/allow-569XZilmsin-string-names_2024-08-12-05-26.json b/common/changes/@rushstack/localization-utilities/allow-569XZilmsin-string-names_2024-08-12-05-26.json new file mode 100644 index 00000000000..b31bfe04576 --- /dev/null +++ b/common/changes/@rushstack/localization-utilities/allow-569XZilmsin-string-names_2024-08-12-05-26.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/localization-utilities", + "comment": "Update the schema for `.loc.json` files to allow string names that include the `$` character.", + "type": "minor" + } + ], + "packageName": "@rushstack/localization-utilities" +} \ No newline at end of file diff --git a/common/changes/@rushstack/node-core-library/allow-569XZilmsin-string-names_2024-08-12-21-24.json b/common/changes/@rushstack/node-core-library/allow-569XZilmsin-string-names_2024-08-12-21-24.json new file mode 100644 index 00000000000..aa653a68842 --- /dev/null +++ b/common/changes/@rushstack/node-core-library/allow-569XZilmsin-string-names_2024-08-12-21-24.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/node-core-library", + "comment": "Add a `ignoreSchemaField` option to the `JsonSchema.validateObject` options to ignore `$schema` properties and add an options object argument to `JsonSchema.validateObjectWithCallback` with the same `ignoreSchemaField` option.", + "type": "minor" + } + ], + "packageName": "@rushstack/node-core-library" +} \ No newline at end of file diff --git a/common/reviews/api/node-core-library.api.md b/common/reviews/api/node-core-library.api.md index 3ad36597629..359426fe265 100644 --- a/common/reviews/api/node-core-library.api.md +++ b/common/reviews/api/node-core-library.api.md @@ -441,7 +441,12 @@ export interface IJsonSchemaLoadOptions { } // @public -export interface IJsonSchemaValidateOptions { +export interface IJsonSchemaValidateObjectWithOptions { + ignoreSchemaField?: boolean; +} + +// @public +export interface IJsonSchemaValidateOptions extends IJsonSchemaValidateObjectWithOptions { customErrorHeader?: string; } @@ -675,7 +680,7 @@ export class JsonSchema { static fromLoadedObject(schemaObject: JsonObject, options?: IJsonSchemaFromObjectOptions): JsonSchema; get shortName(): string; validateObject(jsonObject: JsonObject, filenameForErrors: string, options?: IJsonSchemaValidateOptions): void; - validateObjectWithCallback(jsonObject: JsonObject, errorCallback: (errorInfo: IJsonSchemaErrorInfo) => void): void; + validateObjectWithCallback(jsonObject: JsonObject, errorCallback: (errorInfo: IJsonSchemaErrorInfo) => void, options?: IJsonSchemaValidateObjectWithOptions): void; } // @public diff --git a/libraries/localization-utilities/src/parsers/parseLocJson.ts b/libraries/localization-utilities/src/parsers/parseLocJson.ts index a49d2efdd18..ab267ae5d99 100644 --- a/libraries/localization-utilities/src/parsers/parseLocJson.ts +++ b/libraries/localization-utilities/src/parsers/parseLocJson.ts @@ -14,7 +14,7 @@ const LOC_JSON_SCHEMA: JsonSchema = JsonSchema.fromLoadedObject(locJsonSchema); export function parseLocJson({ content, filePath, ignoreString }: IParseFileOptions): ILocalizationFile { const parsedFile: ILocalizationFile = JsonFile.parseString(content); try { - LOC_JSON_SCHEMA.validateObject(parsedFile, filePath); + LOC_JSON_SCHEMA.validateObject(parsedFile, filePath, { ignoreSchemaField: true }); } catch (e) { throw new Error(`The loc file is invalid. Error: ${e}`); } diff --git a/libraries/localization-utilities/src/schemas/locJson.schema.json b/libraries/localization-utilities/src/schemas/locJson.schema.json index a407d34bf33..60cd6027798 100644 --- a/libraries/localization-utilities/src/schemas/locJson.schema.json +++ b/libraries/localization-utilities/src/schemas/locJson.schema.json @@ -2,14 +2,8 @@ "$schema": "http://json-schema.org/draft-04/schema#", "title": "Localizable JSON file", - "properties": { - "$schema": { - "description": "Part of the JSON Schema standard, this optional keyword declares the URL of the schema that the file conforms to. Editors may download the schema and use it to perform syntax highlighting.", - "type": "string" - } - }, "patternProperties": { - "^[A-Za-z_][0-9A-Za-z_]*$": { + "^[A-Za-z_$][0-9A-Za-z_$]*$": { "type": "object", "properties": { "value": { diff --git a/libraries/node-core-library/src/JsonSchema.ts b/libraries/node-core-library/src/JsonSchema.ts index 6b07f1d16b3..fd68ad65302 100644 --- a/libraries/node-core-library/src/JsonSchema.ts +++ b/libraries/node-core-library/src/JsonSchema.ts @@ -26,7 +26,7 @@ interface ISchemaWithId { export type JsonSchemaVersion = 'draft-04' | 'draft-07'; /** - * Callback function arguments for JsonSchema.validateObjectWithCallback(); + * Callback function arguments for {@link JsonSchema.validateObjectWithCallback} * @public */ export interface IJsonSchemaErrorInfo { @@ -37,10 +37,22 @@ export interface IJsonSchemaErrorInfo { } /** - * Options for JsonSchema.validateObject() + * Options for {@link JsonSchema.validateObjectWithCallback} * @public */ -export interface IJsonSchemaValidateOptions { +export interface IJsonSchemaValidateObjectWithOptions { + /** + * If true, the root-level `$schema` property in a JSON object being validated will be ignored during validation. + * If this is set to `true` and the schema requires a `$schema` property, validation will fail. + */ + ignoreSchemaField?: boolean; +} + +/** + * Options for {@link JsonSchema.validateObject} + * @public + */ +export interface IJsonSchemaValidateOptions extends IJsonSchemaValidateObjectWithOptions { /** * A custom header that will be used to report schema errors. * @remarks @@ -53,7 +65,7 @@ export interface IJsonSchemaValidateOptions { } /** - * Options for JsonSchema.fromFile() and JsonSchema.fromLoadedObject() + * Options for {@link JsonSchema.fromFile} and {@link JsonSchema.fromLoadedObject} * @public */ export interface IJsonSchemaLoadOptions { @@ -85,13 +97,13 @@ export interface IJsonSchemaLoadOptions { } /** - * Options for JsonSchema.fromFile() + * Options for {@link JsonSchema.fromFile} * @public */ export type IJsonSchemaFromFileOptions = IJsonSchemaLoadOptions; /** - * Options for JsonSchema.fromLoadedObject() + * Options for {@link JsonSchema.fromLoadedObject} * @public */ export type IJsonSchemaFromObjectOptions = IJsonSchemaLoadOptions; @@ -334,12 +346,15 @@ export class JsonSchema { filenameForErrors: string, options?: IJsonSchemaValidateOptions ): void { - this.validateObjectWithCallback(jsonObject, (errorInfo: IJsonSchemaErrorInfo) => { - const prefix: string = - options && options.customErrorHeader ? options.customErrorHeader : 'JSON validation failed:'; - - throw new Error(prefix + os.EOL + filenameForErrors + os.EOL + errorInfo.details); - }); + this.validateObjectWithCallback( + jsonObject, + (errorInfo: IJsonSchemaErrorInfo) => { + const prefix: string = options?.customErrorHeader ?? 'JSON validation failed:'; + + throw new Error(prefix + os.EOL + filenameForErrors + os.EOL + errorInfo.details); + }, + options + ); } /** @@ -348,10 +363,20 @@ export class JsonSchema { */ public validateObjectWithCallback( jsonObject: JsonObject, - errorCallback: (errorInfo: IJsonSchemaErrorInfo) => void + errorCallback: (errorInfo: IJsonSchemaErrorInfo) => void, + options?: IJsonSchemaValidateObjectWithOptions ): void { this.ensureCompiled(); + if (options?.ignoreSchemaField) { + const { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + $schema, + ...remainder + } = jsonObject; + jsonObject = remainder; + } + if (this._validator && !this._validator(jsonObject)) { const errorDetails: string = JsonSchema._formatErrorDetails(this._validator.errors!); diff --git a/libraries/node-core-library/src/index.ts b/libraries/node-core-library/src/index.ts index a8d46cdaedd..70396912fbc 100644 --- a/libraries/node-core-library/src/index.ts +++ b/libraries/node-core-library/src/index.ts @@ -69,6 +69,7 @@ export { type IJsonSchemaFromObjectOptions, type IJsonSchemaLoadOptions, type IJsonSchemaValidateOptions, + type IJsonSchemaValidateObjectWithOptions, JsonSchema, type JsonSchemaVersion } from './JsonSchema';