runtime/shared/FbtResultBase.js (160 lines of code) (raw):

/** * (c) Meta Platforms, Inc. and affiliates. Confidential and proprietary. * * This file is shared between www and fbsource and www is the source of truth. * When you make change to this file on www, please make sure you test it on * fbsource and send a diff to update the files too so that the 2 versions are * kept in sync. * * Run the following command to sync the change from www to fbsource. * js1 upgrade www-shared -p fbt --local ~/www * * @format * @flow * @emails oncall+i18n_fbt_js */ 'use strict'; class FbtResultBase implements IFbtResultBase { _contents: $NestedFbtContentItems; _stringValue: ?string; // Helps detect infinite recursion cycles with toString() _isSerializing: boolean; // __errorListener is given an extra "private" underscore to prevent munging // (https://fburl.com/munge) of the member. We access the member in a // function declared on the prototype outside of the class (see below). So, // munging will affect this access. __errorListener: ?IFbtErrorListener; // Declare that we'll implement these methods anchor: $PropertyType<IFbtResultBase, 'anchor'>; big: $PropertyType<IFbtResultBase, 'big'>; blink: $PropertyType<IFbtResultBase, 'blink'>; bold: $PropertyType<IFbtResultBase, 'bold'>; charAt: $PropertyType<IFbtResultBase, 'charAt'>; charCodeAt: $PropertyType<IFbtResultBase, 'charCodeAt'>; codePointAt: $PropertyType<IFbtResultBase, 'codePointAt'>; contains: $PropertyType<IFbtResultBase, 'contains'>; endsWith: $PropertyType<IFbtResultBase, 'endsWith'>; fixed: $PropertyType<IFbtResultBase, 'fixed'>; fontcolor: $PropertyType<IFbtResultBase, 'fontcolor'>; fontsize: $PropertyType<IFbtResultBase, 'fontsize'>; includes: $PropertyType<IFbtResultBase, 'includes'>; indexOf: $PropertyType<IFbtResultBase, 'indexOf'>; italics: $PropertyType<IFbtResultBase, 'italics'>; lastIndexOf: $PropertyType<IFbtResultBase, 'lastIndexOf'>; link: $PropertyType<IFbtResultBase, 'link'>; localeCompare: $PropertyType<IFbtResultBase, 'localeCompare'>; match: $PropertyType<IFbtResultBase, 'match'>; normalize: $PropertyType<IFbtResultBase, 'normalize'>; repeat: $PropertyType<IFbtResultBase, 'repeat'>; replace: $PropertyType<IFbtResultBase, 'replace'>; search: $PropertyType<IFbtResultBase, 'search'>; slice: $PropertyType<IFbtResultBase, 'slice'>; small: $PropertyType<IFbtResultBase, 'small'>; split: $PropertyType<IFbtResultBase, 'split'>; startsWith: $PropertyType<IFbtResultBase, 'startsWith'>; strike: $PropertyType<IFbtResultBase, 'strike'>; sub: $PropertyType<IFbtResultBase, 'sub'>; substr: $PropertyType<IFbtResultBase, 'substr'>; substring: $PropertyType<IFbtResultBase, 'substring'>; sup: $PropertyType<IFbtResultBase, 'sup'>; toLocaleLowerCase: $PropertyType<IFbtResultBase, 'toLocaleLowerCase'>; toLocaleUpperCase: $PropertyType<IFbtResultBase, 'toLocaleUpperCase'>; toLowerCase: $PropertyType<IFbtResultBase, 'toLowerCase'>; toUpperCase: $PropertyType<IFbtResultBase, 'toUpperCase'>; trim: $PropertyType<IFbtResultBase, 'trim'>; trimLeft: $PropertyType<IFbtResultBase, 'trimLeft'>; trimRight: $PropertyType<IFbtResultBase, 'trimRight'>; constructor( contents: $NestedFbtContentItems, errorListener: ?IFbtErrorListener, ) { this._contents = contents; this.__errorListener = errorListener; this._isSerializing = false; this._stringValue = null; } flattenToArray(): Array<$FbtContentItem> { return FbtResultBase.flattenToArray(this._contents); } getContents() { return this._contents; } toString(): string { if (Object.isFrozen(this)) { // we can't alter this._isSerializing // so let's just return the string and risk infinite recursion... return this._toString(); } // Prevent risk of infinite recursions if the error listener or nested contents toString() // reenters this method on the same instance if (this._isSerializing) { return '<<Reentering fbt.toString() is forbidden>>'; } this._isSerializing = true; try { return this._toString(); } finally { this._isSerializing = false; } } _toString(): string { if (this._stringValue != null) { return this._stringValue; } let stringValue = ''; const contents = this.flattenToArray(); for (let ii = 0; ii < contents.length; ++ii) { const content = contents[ii]; if (typeof content === 'string' || content instanceof FbtResultBase) { stringValue += content.toString(); } else { this.__errorListener?.onStringSerializationError?.(content); } } if (!Object.isFrozen(this)) { this._stringValue = stringValue; } return stringValue; } toJSON(): string { return this.toString(); } static flattenToArray( contents: $NestedFbtContentItems, ): Array<$FbtContentItem> { const result = []; for (let ii = 0; ii < contents.length; ++ii) { const content = contents[ii]; if (Array.isArray(content)) { // $FlowFixMe[method-unbinding] added when improving typing for this parameters result.push.apply(result, FbtResultBase.flattenToArray(content)); // $FlowFixMe[incompatible-type] } else if (content instanceof FbtResultBase) { // $FlowFixMe[method-unbinding] added when improving typing for this parameters result.push.apply(result, content.flattenToArray()); } else { result.push(content); } } return result; } } // Warning: The following methods are only appplicable during the transition // period for some existing code that uses string method on Fbt string. // The fbt string should be considered as the final string to be displayed // and therefore should not be manipulated. // The following methods are expected not to be supported "soon". [ 'anchor', 'big', 'blink', 'bold', 'charAt', 'charCodeAt', 'codePointAt', 'contains', 'endsWith', 'fixed', 'fontcolor', 'fontsize', 'includes', 'indexOf', 'italics', 'lastIndexOf', 'link', 'localeCompare', 'match', 'normalize', 'repeat', 'replace', 'search', 'slice', 'small', 'split', 'startsWith', 'strike', 'sub', 'substr', 'substring', 'sup', 'toLocaleLowerCase', 'toLocaleUpperCase', 'toLowerCase', 'toUpperCase', 'trim', 'trimLeft', 'trimRight', ].forEach(methodName => { /* eslint-disable fb-www/should-use-class */ // $FlowFixMe[prop-missing] index signature FbtResultBase.prototype[methodName] = function (...args) { this.__errorListener?.onStringMethodUsed?.(methodName); // $FlowFixMe[incompatible-type] Mock stringish methods // $FlowFixMe[prop-missing] Mock stringish methods return String.prototype[methodName].apply(this, args); }; /* eslint-enable fb-www/should-use-class */ }); module.exports = ((FbtResultBase: $FlowFixMe): Class<$FbtResultBase>);