-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(build): move error-codes generation to transform-error-messages b…
…abel plugin and always run with build-release
- Loading branch information
Showing
8 changed files
with
500 additions
and
167 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
*/ | ||
'use strict'; | ||
// @ts-check | ||
|
||
/** | ||
* Data structure to manage reading from and writing to codes.json | ||
*/ | ||
class ErrorMap { | ||
/** | ||
* The map of error code numbers (as String(number)) to the error messages | ||
* | ||
* @type {Record<string, string>} | ||
*/ | ||
errorMap; | ||
/** | ||
* The map of error messages to the error code numbers (as integers) | ||
* | ||
* @type {Record<string, number} | ||
*/ | ||
inverseErrorMap = {}; | ||
/** | ||
* The largest known error code presently in the errorMap | ||
* @type {number} | ||
*/ | ||
maxId = -1; | ||
/** | ||
* true if the errorMap has been updated but not yet flushed | ||
* | ||
* @type {boolean} | ||
*/ | ||
dirty = false; | ||
|
||
/** | ||
* @param {Record<string, string>} errorMap typically the result of `JSON.parse(fs.readFileSync('codes.json', 'utf8'))` | ||
* @param {(errorMap: Record<string, string>) => void} flushErrorMap the callback to persist the errorMap back to disk | ||
*/ | ||
constructor(errorMap, flushErrorMap) { | ||
this.errorMap = errorMap; | ||
this.flushErrorMap = flushErrorMap; | ||
for (const k in this.errorMap) { | ||
const id = parseInt(k, 10); | ||
this.inverseErrorMap[this.errorMap[k]] = id; | ||
this.maxId = id > this.maxId ? id : this.maxId; | ||
} | ||
} | ||
|
||
/** | ||
* Fetch the error code for a given error message. If the error message is | ||
* present in the errorMap, it will return the corresponding numeric code. | ||
* | ||
* If the message is not present, and extractCodes is not true, it will | ||
* return false. | ||
* | ||
* Otherwise, it will generate a new error code and queue a microtask to | ||
* flush it back to disk (so multiple updates can be batched together). | ||
* | ||
* @param {string} message the error message | ||
* @param {boolean} extractCodes true if we are also writing to codes.json | ||
* @returns {number | undefined} | ||
*/ | ||
getOrAddToErrorMap(message, extractCodes) { | ||
let id = this.inverseErrorMap[message]; | ||
if (extractCodes && typeof id === 'undefined') { | ||
id = ++this.maxId; | ||
this.inverseErrorMap[message] = id; | ||
this.errorMap[`${id}`] = message; | ||
if (!this.dirty) { | ||
queueMicrotask(this.flush.bind(this)); | ||
this.dirty = true; | ||
} | ||
} | ||
return id; | ||
} | ||
|
||
/** | ||
* If dirty is true, this will call flushErrorMap with the current errorMap | ||
* and reset dirty to false. | ||
* | ||
* Normally this is automatically queued to a microtask as necessary, but | ||
* it may be called manually in test scenarios. | ||
*/ | ||
flush() { | ||
if (this.dirty) { | ||
this.flushErrorMap(this.errorMap); | ||
this.dirty = false; | ||
} | ||
} | ||
} | ||
|
||
module.exports = ErrorMap; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/** | ||
* Copyright (c) Meta Platforms, Inc. and affiliates. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
*/ | ||
// @ts-check | ||
'use strict'; | ||
|
||
const ErrorMap = require('../../ErrorMap'); | ||
|
||
/** @returns {Promise<void>} */ | ||
function waitTick() { | ||
return new Promise((resolve) => queueMicrotask(resolve)); | ||
} | ||
|
||
describe('ErrorMap', () => { | ||
[ | ||
{initialMessages: []}, | ||
{ | ||
initialMessages: ['known message', 'another known message'], | ||
}, | ||
].forEach(({name, initialMessages}) => { | ||
const initialMap = Object.fromEntries( | ||
initialMessages.map((message, i) => [`${i}`, message]), | ||
); | ||
describe(`with ${initialMessages.length} message(s)`, () => { | ||
test('does not insert unless extractCodes is true', async () => { | ||
const flush = jest.fn(); | ||
const errorMap = new ErrorMap(initialMap, flush); | ||
expect(errorMap.getOrAddToErrorMap('unknown message', false)).toBe( | ||
undefined, | ||
); | ||
await waitTick(); | ||
expect(flush).not.toBeCalled(); | ||
expect(Object.keys(errorMap.errorMap).length).toEqual( | ||
initialMessages.length, | ||
); | ||
}); | ||
if (initialMessages.length > 0) { | ||
test('looks up existing messages', async () => { | ||
const flush = jest.fn(); | ||
const errorMap = new ErrorMap(initialMap, flush); | ||
initialMessages.forEach((msg, i) => { | ||
expect(errorMap.getOrAddToErrorMap(msg, false)).toBe(i); | ||
}); | ||
expect(errorMap.dirty).toBe(false); | ||
initialMessages.forEach((msg, i) => { | ||
expect(errorMap.getOrAddToErrorMap(msg, true)).toBe(i); | ||
}); | ||
expect(errorMap.dirty).toBe(false); | ||
await waitTick(); | ||
expect(flush).not.toBeCalled(); | ||
}); | ||
} | ||
test('inserts with extractCodes true', async () => { | ||
const flush = jest.fn(); | ||
const errorMap = new ErrorMap(initialMap, flush); | ||
const msg = 'unknown message'; | ||
const beforeSize = initialMessages.length; | ||
expect(errorMap.getOrAddToErrorMap(msg, true)).toBe(beforeSize); | ||
expect(Object.keys(errorMap.errorMap).length).toEqual(1 + beforeSize); | ||
expect(Object.keys(errorMap.inverseErrorMap).length).toEqual( | ||
1 + beforeSize, | ||
); | ||
expect(errorMap.errorMap[beforeSize]).toBe(msg); | ||
expect(errorMap.inverseErrorMap[msg]).toBe(beforeSize); | ||
expect(errorMap.maxId).toBe(beforeSize); | ||
expect(flush).not.toBeCalled(); | ||
expect(errorMap.dirty).toBe(true); | ||
await waitTick(); | ||
expect(errorMap.dirty).toBe(false); | ||
expect(flush).toBeCalledWith({...initialMap, [`${beforeSize}`]: msg}); | ||
}); | ||
test('inserts two messages with extractCodes true', async () => { | ||
const flush = jest.fn(); | ||
const errorMap = new ErrorMap(initialMap, flush); | ||
const msgs = ['unknown message', 'another unknown message']; | ||
msgs.forEach((msg, i) => { | ||
const beforeSize = i + initialMessages.length; | ||
expect(errorMap.getOrAddToErrorMap(msg, true)).toBe(beforeSize); | ||
expect(Object.keys(errorMap.errorMap).length).toEqual(1 + beforeSize); | ||
expect(Object.keys(errorMap.inverseErrorMap).length).toEqual( | ||
1 + beforeSize, | ||
); | ||
expect(errorMap.errorMap[beforeSize]).toBe(msg); | ||
expect(errorMap.inverseErrorMap[msg]).toBe(beforeSize); | ||
expect(errorMap.maxId).toBe(beforeSize); | ||
expect(flush).not.toBeCalled(); | ||
}); | ||
expect(errorMap.dirty).toBe(true); | ||
await waitTick(); | ||
expect(errorMap.dirty).toBe(false); | ||
expect(flush).toBeCalledTimes(1); | ||
expect(flush).toBeCalledWith({ | ||
...initialMap, | ||
...Object.fromEntries( | ||
msgs.map((msg, i) => [`${initialMessages.length + i}`, msg]), | ||
), | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.