src/parsers/locale-messagesjson.js (139 lines of code) (raw):

import RJSON from '@fregante/relaxed-json'; import * as messages from 'messages'; import JSONParser from 'parsers/json'; import { MESSAGES_JSON, MESSAGE_PLACEHOLDER_REGEXP } from 'const'; import { validateLocaleMessages } from 'schema/validator'; import log from 'logger'; export default class LocaleMessagesJSONParser extends JSONParser { constructor( jsonString, collector, addonMetadata, { filename = MESSAGES_JSON, RelaxedJSON = RJSON } = {} ) { super(jsonString, collector, addonMetadata, { filename }); this.relaxedJSON = RelaxedJSON; } parse() { super.parse(this.relaxedJSON); this.lowercasePlaceholders = {}; // Set up some defaults in case parsing fails. if (typeof this.parsedJSON === 'undefined' || this.isValid === false) { this.parsedJSON = {}; } else { // We've parsed the JSON; now we can validate the manifest. this._validate(); } } errorLookup(error) { // This is the default message. let baseObject = messages.JSON_INVALID; const overrides = { instancePath: error.instancePath, line: error.line, file: this.filename, }; // Missing the message property. if (error.keyword === 'required') { if (error.params.missingProperty === 'message') { baseObject = messages.NO_MESSAGE; } else if (error.params.missingProperty === 'content') { baseObject = messages.NO_PLACEHOLDER_CONTENT; } } else if (error.keyword === 'additionalProperties') { if ( error.schemaPath === '#/properties/placeholders/additionalProperties' ) { baseObject = messages.INVALID_PLACEHOLDER_NAME; } } return { ...baseObject, ...overrides }; } getLowercasePlaceholders(message) { const messageObj = this.parsedJSON[message]; if (!Object.prototype.hasOwnProperty.call(messageObj, 'placeholders')) { return undefined; } if ( !Object.prototype.hasOwnProperty.call(this.lowercasePlaceholders, message) ) { this.lowercasePlaceholders[message] = Object.keys( messageObj.placeholders ).map((placeholder) => placeholder.toLowerCase()); } return this.lowercasePlaceholders[message]; } hasPlaceholder(message, placeholder) { const messageObj = this.parsedJSON[message]; return ( Object.prototype.hasOwnProperty.call(messageObj, 'placeholders') && this.getLowercasePlaceholders(message).includes(placeholder.toLowerCase()) ); } _validate() { this.isValid = validateLocaleMessages(this.parsedJSON); if (!this.isValid) { log.debug('Schema Validation messages', validateLocaleMessages.errors); validateLocaleMessages.errors.forEach((error) => { const message = this.errorLookup(error); this.collector.addError(message); }); } const regexp = new RegExp(MESSAGE_PLACEHOLDER_REGEXP, 'ig'); const visitedLowercaseMessages = []; Object.keys(this.parsedJSON).forEach((message) => { if (!visitedLowercaseMessages.includes(message.toLowerCase())) { visitedLowercaseMessages.push(message.toLowerCase()); } else { this.collector.addError({ ...messages.JSON_DUPLICATE_KEY, file: this.filename, description: `Case-insensitive duplicate message name: ${message} found in JSON`, instancePath: `/${message}`, }); this.isValid = false; } if (message.startsWith('@@')) { this.collector.addWarning({ file: this.filename, instancePath: `/${message}`, ...messages.PREDEFINED_MESSAGE_NAME, }); } const messageContent = this.parsedJSON[message].message; let matches = regexp.exec(messageContent); while (matches !== null) { if (!this.hasPlaceholder(message, matches[1])) { this.collector.addWarning({ file: this.filename, instancePath: `/${message}/placeholders/${matches[1]}`, ...messages.MISSING_PLACEHOLDER, }); } matches = regexp.exec(messageContent); } if ( Object.prototype.hasOwnProperty.call( this.parsedJSON[message], 'placeholders' ) ) { const visitedLowercasePlaceholders = []; Object.keys(this.parsedJSON[message].placeholders).forEach( (placeholder) => { if ( !visitedLowercasePlaceholders.includes(placeholder.toLowerCase()) ) { visitedLowercasePlaceholders.push(placeholder.toLowerCase()); } else { this.collector.addError({ ...messages.JSON_DUPLICATE_KEY, file: this.filename, description: `Case-insensitive duplicate placeholder name: ${placeholder} found in JSON`, instancePath: `/${message}/placeholders/${placeholder}`, }); this.isValid = false; } } ); } // Reset the regexp regexp.lastIndex = 0; }); } }