errors.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793
  1. /**
  2. * All errors in ethers include properties to ensure they are both
  3. * human-readable (i.e. ``.message``) and machine-readable (i.e. ``.code``).
  4. *
  5. * The [[isError]] function can be used to check the error ``code`` and
  6. * provide a type guard for the properties present on that error interface.
  7. *
  8. * @_section: api/utils/errors:Errors [about-errors]
  9. */
  10. import { version } from "../_version.js";
  11. import { defineProperties } from "./properties.js";
  12. import type {
  13. TransactionRequest, TransactionReceipt, TransactionResponse
  14. } from "../providers/index.js";
  15. import type { FetchRequest, FetchResponse } from "./fetch.js";
  16. /**
  17. * An error may contain additional properties, but those must not
  18. * conflict with any implicit properties.
  19. */
  20. export type ErrorInfo<T> = Omit<T, "code" | "name" | "message" | "shortMessage"> & { shortMessage?: string };
  21. function stringify(value: any): any {
  22. if (value == null) { return "null"; }
  23. if (Array.isArray(value)) {
  24. return "[ " + (value.map(stringify)).join(", ") + " ]";
  25. }
  26. if (value instanceof Uint8Array) {
  27. const HEX = "0123456789abcdef";
  28. let result = "0x";
  29. for (let i = 0; i < value.length; i++) {
  30. result += HEX[value[i] >> 4];
  31. result += HEX[value[i] & 0xf];
  32. }
  33. return result;
  34. }
  35. if (typeof(value) === "object" && typeof(value.toJSON) === "function") {
  36. return stringify(value.toJSON());
  37. }
  38. switch (typeof(value)) {
  39. case "boolean": case "symbol":
  40. return value.toString();
  41. case "bigint":
  42. return BigInt(value).toString();
  43. case "number":
  44. return (value).toString();
  45. case "string":
  46. return JSON.stringify(value);
  47. case "object": {
  48. const keys = Object.keys(value);
  49. keys.sort();
  50. return "{ " + keys.map((k) => `${ stringify(k) }: ${ stringify(value[k]) }`).join(", ") + " }";
  51. }
  52. }
  53. return `[ COULD NOT SERIALIZE ]`;
  54. }
  55. /**
  56. * All errors emitted by ethers have an **ErrorCode** to help
  57. * identify and coalesce errors to simplify programmatic analysis.
  58. *
  59. * Each **ErrorCode** is the %%code%% proerty of a coresponding
  60. * [[EthersError]].
  61. *
  62. * **Generic Errors**
  63. *
  64. * **``"UNKNOWN_ERROR"``** - see [[UnknownError]]
  65. *
  66. * **``"NOT_IMPLEMENTED"``** - see [[NotImplementedError]]
  67. *
  68. * **``"UNSUPPORTED_OPERATION"``** - see [[UnsupportedOperationError]]
  69. *
  70. * **``"NETWORK_ERROR"``** - see [[NetworkError]]
  71. *
  72. * **``"SERVER_ERROR"``** - see [[ServerError]]
  73. *
  74. * **``"TIMEOUT"``** - see [[TimeoutError]]
  75. *
  76. * **``"BAD_DATA"``** - see [[BadDataError]]
  77. *
  78. * **``"CANCELLED"``** - see [[CancelledError]]
  79. *
  80. * **Operational Errors**
  81. *
  82. * **``"BUFFER_OVERRUN"``** - see [[BufferOverrunError]]
  83. *
  84. * **``"NUMERIC_FAULT"``** - see [[NumericFaultError]]
  85. *
  86. * **Argument Errors**
  87. *
  88. * **``"INVALID_ARGUMENT"``** - see [[InvalidArgumentError]]
  89. *
  90. * **``"MISSING_ARGUMENT"``** - see [[MissingArgumentError]]
  91. *
  92. * **``"UNEXPECTED_ARGUMENT"``** - see [[UnexpectedArgumentError]]
  93. *
  94. * **``"VALUE_MISMATCH"``** - //unused//
  95. *
  96. * **Blockchain Errors**
  97. *
  98. * **``"CALL_EXCEPTION"``** - see [[CallExceptionError]]
  99. *
  100. * **``"INSUFFICIENT_FUNDS"``** - see [[InsufficientFundsError]]
  101. *
  102. * **``"NONCE_EXPIRED"``** - see [[NonceExpiredError]]
  103. *
  104. * **``"REPLACEMENT_UNDERPRICED"``** - see [[ReplacementUnderpricedError]]
  105. *
  106. * **``"TRANSACTION_REPLACED"``** - see [[TransactionReplacedError]]
  107. *
  108. * **``"UNCONFIGURED_NAME"``** - see [[UnconfiguredNameError]]
  109. *
  110. * **``"OFFCHAIN_FAULT"``** - see [[OffchainFaultError]]
  111. *
  112. * **User Interaction Errors**
  113. *
  114. * **``"ACTION_REJECTED"``** - see [[ActionRejectedError]]
  115. */
  116. export type ErrorCode =
  117. // Generic Errors
  118. "UNKNOWN_ERROR" | "NOT_IMPLEMENTED" | "UNSUPPORTED_OPERATION" |
  119. "NETWORK_ERROR" | "SERVER_ERROR" | "TIMEOUT" | "BAD_DATA" |
  120. "CANCELLED" |
  121. // Operational Errors
  122. "BUFFER_OVERRUN" | "NUMERIC_FAULT" |
  123. // Argument Errors
  124. "INVALID_ARGUMENT" | "MISSING_ARGUMENT" | "UNEXPECTED_ARGUMENT" |
  125. "VALUE_MISMATCH" |
  126. // Blockchain Errors
  127. "CALL_EXCEPTION" | "INSUFFICIENT_FUNDS" | "NONCE_EXPIRED" |
  128. "REPLACEMENT_UNDERPRICED" | "TRANSACTION_REPLACED" |
  129. "UNCONFIGURED_NAME" | "OFFCHAIN_FAULT" |
  130. // User Interaction
  131. "ACTION_REJECTED"
  132. ;
  133. /**
  134. * All errors in Ethers include properties to assist in
  135. * machine-readable errors.
  136. */
  137. export interface EthersError<T extends ErrorCode = ErrorCode> extends Error {
  138. /**
  139. * The string error code.
  140. */
  141. code: ErrorCode;
  142. /**
  143. * A short message describing the error, with minimal additional
  144. * details.
  145. */
  146. shortMessage: string;
  147. /**
  148. * Additional info regarding the error that may be useful.
  149. *
  150. * This is generally helpful mostly for human-based debugging.
  151. */
  152. info?: Record<string, any>;
  153. /**
  154. * Any related error.
  155. */
  156. error?: Error;
  157. }
  158. // Generic Errors
  159. /**
  160. * This Error is a catch-all for when there is no way for Ethers to
  161. * know what the underlying problem is.
  162. */
  163. export interface UnknownError extends EthersError<"UNKNOWN_ERROR"> {
  164. [ key: string ]: any;
  165. }
  166. /**
  167. * This Error is mostly used as a stub for functionality that is
  168. * intended for the future, but is currently not implemented.
  169. */
  170. export interface NotImplementedError extends EthersError<"NOT_IMPLEMENTED"> {
  171. /**
  172. * The attempted operation.
  173. */
  174. operation: string;
  175. }
  176. /**
  177. * This Error indicates that the attempted operation is not supported.
  178. *
  179. * This could range from a specific JSON-RPC end-point not supporting
  180. * a feature to a specific configuration of an object prohibiting the
  181. * operation.
  182. *
  183. * For example, a [[Wallet]] with no connected [[Provider]] is unable
  184. * to send a transaction.
  185. */
  186. export interface UnsupportedOperationError extends EthersError<"UNSUPPORTED_OPERATION"> {
  187. /**
  188. * The attempted operation.
  189. */
  190. operation: string;
  191. }
  192. /**
  193. * This Error indicates a problem connecting to a network.
  194. */
  195. export interface NetworkError extends EthersError<"NETWORK_ERROR"> {
  196. /**
  197. * The network event.
  198. */
  199. event: string;
  200. }
  201. /**
  202. * This Error indicates there was a problem fetching a resource from
  203. * a server.
  204. */
  205. export interface ServerError extends EthersError<"SERVER_ERROR"> {
  206. /**
  207. * The requested resource.
  208. */
  209. request: FetchRequest | string;
  210. /**
  211. * The response received from the server, if available.
  212. */
  213. response?: FetchResponse;
  214. }
  215. /**
  216. * This Error indicates that the timeout duration has expired and
  217. * that the operation has been implicitly cancelled.
  218. *
  219. * The side-effect of the operation may still occur, as this
  220. * generally means a request has been sent and there has simply
  221. * been no response to indicate whether it was processed or not.
  222. */
  223. export interface TimeoutError extends EthersError<"TIMEOUT"> {
  224. /**
  225. * The attempted operation.
  226. */
  227. operation: string;
  228. /**
  229. * The reason.
  230. */
  231. reason: string;
  232. /**
  233. * The resource request, if available.
  234. */
  235. request?: FetchRequest;
  236. }
  237. /**
  238. * This Error indicates that a provided set of data cannot
  239. * be correctly interpreted.
  240. */
  241. export interface BadDataError extends EthersError<"BAD_DATA"> {
  242. /**
  243. * The data.
  244. */
  245. value: any;
  246. }
  247. /**
  248. * This Error indicates that the operation was cancelled by a
  249. * programmatic call, for example to ``cancel()``.
  250. */
  251. export interface CancelledError extends EthersError<"CANCELLED"> {
  252. }
  253. // Operational Errors
  254. /**
  255. * This Error indicates an attempt was made to read outside the bounds
  256. * of protected data.
  257. *
  258. * Most operations in Ethers are protected by bounds checks, to mitigate
  259. * exploits when parsing data.
  260. */
  261. export interface BufferOverrunError extends EthersError<"BUFFER_OVERRUN"> {
  262. /**
  263. * The buffer that was overrun.
  264. */
  265. buffer: Uint8Array;
  266. /**
  267. * The length of the buffer.
  268. */
  269. length: number;
  270. /**
  271. * The offset that was requested.
  272. */
  273. offset: number;
  274. }
  275. /**
  276. * This Error indicates an operation which would result in incorrect
  277. * arithmetic output has occurred.
  278. *
  279. * For example, trying to divide by zero or using a ``uint8`` to store
  280. * a negative value.
  281. */
  282. export interface NumericFaultError extends EthersError<"NUMERIC_FAULT"> {
  283. /**
  284. * The attempted operation.
  285. */
  286. operation: string;
  287. /**
  288. * The fault reported.
  289. */
  290. fault: string;
  291. /**
  292. * The value the operation was attempted against.
  293. */
  294. value: any;
  295. }
  296. // Argument Errors
  297. /**
  298. * This Error indicates an incorrect type or value was passed to
  299. * a function or method.
  300. */
  301. export interface InvalidArgumentError extends EthersError<"INVALID_ARGUMENT"> {
  302. /**
  303. * The name of the argument.
  304. */
  305. argument: string;
  306. /**
  307. * The value that was provided.
  308. */
  309. value: any;
  310. info?: Record<string, any>
  311. }
  312. /**
  313. * This Error indicates there were too few arguments were provided.
  314. */
  315. export interface MissingArgumentError extends EthersError<"MISSING_ARGUMENT"> {
  316. /**
  317. * The number of arguments received.
  318. */
  319. count: number;
  320. /**
  321. * The number of arguments expected.
  322. */
  323. expectedCount: number;
  324. }
  325. /**
  326. * This Error indicates too many arguments were provided.
  327. */
  328. export interface UnexpectedArgumentError extends EthersError<"UNEXPECTED_ARGUMENT"> {
  329. /**
  330. * The number of arguments received.
  331. */
  332. count: number;
  333. /**
  334. * The number of arguments expected.
  335. */
  336. expectedCount: number;
  337. }
  338. // Blockchain Errors
  339. /**
  340. * The action that resulted in the call exception.
  341. */
  342. export type CallExceptionAction = "call" | "estimateGas" | "getTransactionResult" | "sendTransaction" | "unknown";
  343. /**
  344. * The related transaction that caused the error.
  345. */
  346. export type CallExceptionTransaction = {
  347. to: null | string;
  348. from?: string;
  349. data: string;
  350. };
  351. /**
  352. * This **Error** indicates a transaction reverted.
  353. */
  354. export interface CallExceptionError extends EthersError<"CALL_EXCEPTION"> {
  355. /**
  356. * The action being performed when the revert was encountered.
  357. */
  358. action: CallExceptionAction;
  359. /**
  360. * The revert data returned.
  361. */
  362. data: null | string;
  363. /**
  364. * A human-readable representation of data, if possible.
  365. */
  366. reason: null | string;
  367. /**
  368. * The transaction that triggered the exception.
  369. */
  370. transaction: CallExceptionTransaction,
  371. /**
  372. * The contract invocation details, if available.
  373. */
  374. invocation: null | {
  375. method: string;
  376. signature: string;
  377. args: Array<any>;
  378. }
  379. /**
  380. * The built-in or custom revert error, if available
  381. */
  382. revert: null | {
  383. signature: string;
  384. name: string;
  385. args: Array<any>;
  386. }
  387. /**
  388. * If the error occurred in a transaction that was mined
  389. * (with a status of ``0``), this is the receipt.
  390. */
  391. receipt?: TransactionReceipt; // @TODO: in v7, make this `null | TransactionReceipt`
  392. }
  393. /**
  394. * The sending account has insufficient funds to cover the
  395. * entire transaction cost.
  396. */
  397. export interface InsufficientFundsError extends EthersError<"INSUFFICIENT_FUNDS"> {
  398. /**
  399. * The transaction.
  400. */
  401. transaction: TransactionRequest;
  402. }
  403. /**
  404. * The sending account has already used this nonce in a
  405. * transaction that has been included.
  406. */
  407. export interface NonceExpiredError extends EthersError<"NONCE_EXPIRED"> {
  408. /**
  409. * The transaction.
  410. */
  411. transaction: TransactionRequest;
  412. }
  413. /**
  414. * A CCIP-read exception, which cannot be recovered from or
  415. * be further processed.
  416. */
  417. export interface OffchainFaultError extends EthersError<"OFFCHAIN_FAULT"> {
  418. /**
  419. * The transaction.
  420. */
  421. transaction?: TransactionRequest;
  422. /**
  423. * The reason the CCIP-read failed.
  424. */
  425. reason: string;
  426. }
  427. /**
  428. * An attempt was made to replace a transaction, but with an
  429. * insufficient additional fee to afford evicting the old
  430. * transaction from the memory pool.
  431. */
  432. export interface ReplacementUnderpricedError extends EthersError<"REPLACEMENT_UNDERPRICED"> {
  433. /**
  434. * The transaction.
  435. */
  436. transaction: TransactionRequest;
  437. }
  438. /**
  439. * A pending transaction was replaced by another.
  440. */
  441. export interface TransactionReplacedError extends EthersError<"TRANSACTION_REPLACED"> {
  442. /**
  443. * If the transaction was cancelled, such that the original
  444. * effects of the transaction cannot be assured.
  445. */
  446. cancelled: boolean;
  447. /**
  448. * The reason the transaction was replaced.
  449. */
  450. reason: "repriced" | "cancelled" | "replaced";
  451. /**
  452. * The hash of the replaced transaction.
  453. */
  454. hash: string;
  455. /**
  456. * The transaction that replaced the transaction.
  457. */
  458. replacement: TransactionResponse;
  459. /**
  460. * The receipt of the transaction that replace the transaction.
  461. */
  462. receipt: TransactionReceipt;
  463. }
  464. /**
  465. * This Error indicates an ENS name was used, but the name has not
  466. * been configured.
  467. *
  468. * This could indicate an ENS name is unowned or that the current
  469. * address being pointed to is the [[ZeroAddress]].
  470. */
  471. export interface UnconfiguredNameError extends EthersError<"UNCONFIGURED_NAME"> {
  472. /**
  473. * The ENS name that was requested
  474. */
  475. value: string;
  476. }
  477. /**
  478. * This Error indicates a request was rejected by the user.
  479. *
  480. * In most clients (such as MetaMask), when an operation requires user
  481. * authorization (such as ``signer.sendTransaction``), the client
  482. * presents a dialog box to the user. If the user denies the request
  483. * this error is thrown.
  484. */
  485. export interface ActionRejectedError extends EthersError<"ACTION_REJECTED"> {
  486. /**
  487. * The requested action.
  488. */
  489. action: "requestAccess" | "sendTransaction" | "signMessage" | "signTransaction" | "signTypedData" | "unknown",
  490. /**
  491. * The reason the action was rejected.
  492. *
  493. * If there is already a pending request, some clients may indicate
  494. * there is already a ``"pending"`` action. This prevents an app
  495. * from spamming the user.
  496. */
  497. reason: "expired" | "rejected" | "pending"
  498. }
  499. // Coding; converts an ErrorCode its Typed Error
  500. /**
  501. * A conditional type that transforms the [[ErrorCode]] T into
  502. * its EthersError type.
  503. *
  504. * @flatworm-skip-docs
  505. */
  506. export type CodedEthersError<T> =
  507. T extends "UNKNOWN_ERROR" ? UnknownError:
  508. T extends "NOT_IMPLEMENTED" ? NotImplementedError:
  509. T extends "UNSUPPORTED_OPERATION" ? UnsupportedOperationError:
  510. T extends "NETWORK_ERROR" ? NetworkError:
  511. T extends "SERVER_ERROR" ? ServerError:
  512. T extends "TIMEOUT" ? TimeoutError:
  513. T extends "BAD_DATA" ? BadDataError:
  514. T extends "CANCELLED" ? CancelledError:
  515. T extends "BUFFER_OVERRUN" ? BufferOverrunError:
  516. T extends "NUMERIC_FAULT" ? NumericFaultError:
  517. T extends "INVALID_ARGUMENT" ? InvalidArgumentError:
  518. T extends "MISSING_ARGUMENT" ? MissingArgumentError:
  519. T extends "UNEXPECTED_ARGUMENT" ? UnexpectedArgumentError:
  520. T extends "CALL_EXCEPTION" ? CallExceptionError:
  521. T extends "INSUFFICIENT_FUNDS" ? InsufficientFundsError:
  522. T extends "NONCE_EXPIRED" ? NonceExpiredError:
  523. T extends "OFFCHAIN_FAULT" ? OffchainFaultError:
  524. T extends "REPLACEMENT_UNDERPRICED" ? ReplacementUnderpricedError:
  525. T extends "TRANSACTION_REPLACED" ? TransactionReplacedError:
  526. T extends "UNCONFIGURED_NAME" ? UnconfiguredNameError:
  527. T extends "ACTION_REJECTED" ? ActionRejectedError:
  528. never;
  529. /**
  530. * Returns true if the %%error%% matches an error thrown by ethers
  531. * that matches the error %%code%%.
  532. *
  533. * In TypeScript environments, this can be used to check that %%error%%
  534. * matches an EthersError type, which means the expected properties will
  535. * be set.
  536. *
  537. * @See [ErrorCodes](api:ErrorCode)
  538. * @example
  539. * try {
  540. * // code....
  541. * } catch (e) {
  542. * if (isError(e, "CALL_EXCEPTION")) {
  543. * // The Type Guard has validated this object
  544. * console.log(e.data);
  545. * }
  546. * }
  547. */
  548. export function isError<K extends ErrorCode, T extends CodedEthersError<K>>(error: any, code: K): error is T {
  549. return (error && (<EthersError>error).code === code);
  550. }
  551. /**
  552. * Returns true if %%error%% is a [[CallExceptionError].
  553. */
  554. export function isCallException(error: any): error is CallExceptionError {
  555. return isError(error, "CALL_EXCEPTION");
  556. }
  557. /**
  558. * Returns a new Error configured to the format ethers emits errors, with
  559. * the %%message%%, [[api:ErrorCode]] %%code%% and additional properties
  560. * for the corresponding EthersError.
  561. *
  562. * Each error in ethers includes the version of ethers, a
  563. * machine-readable [[ErrorCode]], and depending on %%code%%, additional
  564. * required properties. The error message will also include the %%message%%,
  565. * ethers version, %%code%% and all additional properties, serialized.
  566. */
  567. export function makeError<K extends ErrorCode, T extends CodedEthersError<K>>(message: string, code: K, info?: ErrorInfo<T>): T {
  568. let shortMessage = message;
  569. {
  570. const details: Array<string> = [];
  571. if (info) {
  572. if ("message" in info || "code" in info || "name" in info) {
  573. throw new Error(`value will overwrite populated values: ${ stringify(info) }`);
  574. }
  575. for (const key in info) {
  576. if (key === "shortMessage") { continue; }
  577. const value = <any>(info[<keyof ErrorInfo<T>>key]);
  578. // try {
  579. details.push(key + "=" + stringify(value));
  580. // } catch (error: any) {
  581. // console.log("MMM", error.message);
  582. // details.push(key + "=[could not serialize object]");
  583. // }
  584. }
  585. }
  586. details.push(`code=${ code }`);
  587. details.push(`version=${ version }`);
  588. if (details.length) {
  589. message += " (" + details.join(", ") + ")";
  590. }
  591. }
  592. let error;
  593. switch (code) {
  594. case "INVALID_ARGUMENT":
  595. error = new TypeError(message);
  596. break;
  597. case "NUMERIC_FAULT":
  598. case "BUFFER_OVERRUN":
  599. error = new RangeError(message);
  600. break;
  601. default:
  602. error = new Error(message);
  603. }
  604. defineProperties<EthersError>(<EthersError>error, { code });
  605. if (info) { Object.assign(error, info); }
  606. if ((<any>error).shortMessage == null) {
  607. defineProperties<EthersError>(<EthersError>error, { shortMessage });
  608. }
  609. return <T>error;
  610. }
  611. /**
  612. * Throws an EthersError with %%message%%, %%code%% and additional error
  613. * %%info%% when %%check%% is falsish..
  614. *
  615. * @see [[api:makeError]]
  616. */
  617. export function assert<K extends ErrorCode, T extends CodedEthersError<K>>(check: unknown, message: string, code: K, info?: ErrorInfo<T>): asserts check {
  618. if (!check) { throw makeError(message, code, info); }
  619. }
  620. /**
  621. * A simple helper to simply ensuring provided arguments match expected
  622. * constraints, throwing if not.
  623. *
  624. * In TypeScript environments, the %%check%% has been asserted true, so
  625. * any further code does not need additional compile-time checks.
  626. */
  627. export function assertArgument(check: unknown, message: string, name: string, value: unknown): asserts check {
  628. assert(check, message, "INVALID_ARGUMENT", { argument: name, value: value });
  629. }
  630. export function assertArgumentCount(count: number, expectedCount: number, message?: string): void {
  631. if (message == null) { message = ""; }
  632. if (message) { message = ": " + message; }
  633. assert(count >= expectedCount, "missing argument" + message, "MISSING_ARGUMENT", {
  634. count: count,
  635. expectedCount: expectedCount
  636. });
  637. assert(count <= expectedCount, "too many arguments" + message, "UNEXPECTED_ARGUMENT", {
  638. count: count,
  639. expectedCount: expectedCount
  640. });
  641. }
  642. const _normalizeForms = ["NFD", "NFC", "NFKD", "NFKC"].reduce((accum, form) => {
  643. try {
  644. // General test for normalize
  645. /* c8 ignore start */
  646. if ("test".normalize(form) !== "test") { throw new Error("bad"); };
  647. /* c8 ignore stop */
  648. if (form === "NFD") {
  649. const check = String.fromCharCode(0xe9).normalize("NFD");
  650. const expected = String.fromCharCode(0x65, 0x0301)
  651. /* c8 ignore start */
  652. if (check !== expected) { throw new Error("broken") }
  653. /* c8 ignore stop */
  654. }
  655. accum.push(form);
  656. } catch(error) { }
  657. return accum;
  658. }, <Array<string>>[]);
  659. /**
  660. * Throws if the normalization %%form%% is not supported.
  661. */
  662. export function assertNormalize(form: string): void {
  663. assert(_normalizeForms.indexOf(form) >= 0, "platform missing String.prototype.normalize", "UNSUPPORTED_OPERATION", {
  664. operation: "String.prototype.normalize", info: { form }
  665. });
  666. }
  667. /**
  668. * Many classes use file-scoped values to guard the constructor,
  669. * making it effectively private. This facilitates that pattern
  670. * by ensuring the %%givenGaurd%% matches the file-scoped %%guard%%,
  671. * throwing if not, indicating the %%className%% if provided.
  672. */
  673. export function assertPrivate(givenGuard: any, guard: any, className?: string): void {
  674. if (className == null) { className = ""; }
  675. if (givenGuard !== guard) {
  676. let method = className, operation = "new";
  677. if (className) {
  678. method += ".";
  679. operation += " " + className;
  680. }
  681. assert(false, `private constructor; use ${ method }from* methods`, "UNSUPPORTED_OPERATION", {
  682. operation
  683. });
  684. }
  685. }