development.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  1. import {dequal} from 'dequal'
  2. /**
  3. * @type {Set<string>}
  4. */
  5. const codesWarned = new Set()
  6. class AssertionError extends Error {
  7. name = /** @type {const} */ ('Assertion')
  8. code = /** @type {const} */ ('ERR_ASSERTION')
  9. /**
  10. * Create an assertion error.
  11. *
  12. * @param {string} message
  13. * Message explaining error.
  14. * @param {unknown} actual
  15. * Value.
  16. * @param {unknown} expected
  17. * Baseline.
  18. * @param {string} operator
  19. * Name of equality operation.
  20. * @param {boolean} generated
  21. * Whether `message` is a custom message or not
  22. * @returns
  23. * Instance.
  24. */
  25. // eslint-disable-next-line max-params
  26. constructor(message, actual, expected, operator, generated) {
  27. super(message)
  28. if (Error.captureStackTrace) {
  29. Error.captureStackTrace(this, this.constructor)
  30. }
  31. /**
  32. * @type {unknown}
  33. */
  34. this.actual = actual
  35. /**
  36. * @type {unknown}
  37. */
  38. this.expected = expected
  39. /**
  40. * @type {boolean}
  41. */
  42. this.generated = generated
  43. /**
  44. * @type {string}
  45. */
  46. this.operator = operator
  47. }
  48. }
  49. class DeprecationError extends Error {
  50. name = /** @type {const} */ ('DeprecationWarning')
  51. /**
  52. * Create a deprecation message.
  53. *
  54. * @param {string} message
  55. * Message explaining deprecation.
  56. * @param {string | undefined} code
  57. * Deprecation identifier; deprecation messages will be generated only once per code.
  58. * @returns
  59. * Instance.
  60. */
  61. constructor(message, code) {
  62. super(message)
  63. /**
  64. * @type {string | undefined}
  65. */
  66. this.code = code
  67. }
  68. }
  69. /**
  70. * Wrap a function or class to show a deprecation message when first called.
  71. *
  72. * > 👉 **Important**: only shows a message when the `development` condition is
  73. * > used, does nothing in production.
  74. *
  75. * When the resulting wrapped `fn` is called, emits a warning once to
  76. * `console.error` (`stderr`).
  77. * If a code is given, one warning message will be emitted in total per code.
  78. *
  79. * @template {Function} T
  80. * Function or class kind.
  81. * @param {T} fn
  82. * Function or class.
  83. * @param {string} message
  84. * Message explaining deprecation.
  85. * @param {string | null | undefined} [code]
  86. * Deprecation identifier (optional); deprecation messages will be generated
  87. * only once per code.
  88. * @returns {T}
  89. * Wrapped `fn`.
  90. */
  91. export function deprecate(fn, message, code) {
  92. let warned = false
  93. // The wrapper will keep the same prototype as fn to maintain prototype chain
  94. Object.setPrototypeOf(deprecated, fn)
  95. // @ts-expect-error: it’s perfect, typescript…
  96. return deprecated
  97. /**
  98. * @this {unknown}
  99. * @param {...Array<unknown>} args
  100. * @returns {unknown}
  101. */
  102. function deprecated(...args) {
  103. if (!warned) {
  104. warned = true
  105. if (typeof code === 'string' && codesWarned.has(code)) {
  106. // Empty.
  107. } else {
  108. console.error(new DeprecationError(message, code || undefined))
  109. if (typeof code === 'string') codesWarned.add(code)
  110. }
  111. }
  112. return new.target
  113. ? Reflect.construct(fn, args, new.target)
  114. : Reflect.apply(fn, this, args)
  115. }
  116. }
  117. /**
  118. * Assert deep strict equivalence.
  119. *
  120. * > 👉 **Important**: only asserts when the `development` condition is used,
  121. * > does nothing in production.
  122. *
  123. * @template {unknown} T
  124. * Expected kind.
  125. * @param {unknown} actual
  126. * Value.
  127. * @param {T} expected
  128. * Baseline.
  129. * @param {Error | string | null | undefined} [message]
  130. * Message for assertion error (default: `'Expected values to be deeply equal'`).
  131. * @returns {asserts actual is T}
  132. * Nothing; throws when `actual` is not deep strict equal to `expected`.
  133. * @throws {AssertionError}
  134. * Throws when `actual` is not deep strict equal to `expected`.
  135. */
  136. export function equal(actual, expected, message) {
  137. assert(
  138. dequal(actual, expected),
  139. actual,
  140. expected,
  141. 'equal',
  142. 'Expected values to be deeply equal',
  143. message
  144. )
  145. }
  146. /**
  147. * Assert if `value` is truthy.
  148. *
  149. * > 👉 **Important**: only asserts when the `development` condition is used,
  150. * > does nothing in production.
  151. *
  152. * @param {unknown} value
  153. * Value to assert.
  154. * @param {Error | string | null | undefined} [message]
  155. * Message for assertion error (default: `'Expected value to be truthy'`).
  156. * @returns {asserts value}
  157. * Nothing; throws when `value` is falsey.
  158. * @throws {AssertionError}
  159. * Throws when `value` is falsey.
  160. */
  161. export function ok(value, message) {
  162. assert(
  163. Boolean(value),
  164. false,
  165. true,
  166. 'ok',
  167. 'Expected value to be truthy',
  168. message
  169. )
  170. }
  171. /**
  172. * Assert that a code path never happens.
  173. *
  174. * > 👉 **Important**: only asserts when the `development` condition is used,
  175. * > does nothing in production.
  176. *
  177. * @param {Error | string | null | undefined} [message]
  178. * Message for assertion error (default: `'Unreachable'`).
  179. * @returns {never}
  180. * Nothing; always throws.
  181. * @throws {AssertionError}
  182. * Throws when `value` is falsey.
  183. */
  184. export function unreachable(message) {
  185. assert(false, false, true, 'ok', 'Unreachable', message)
  186. }
  187. /**
  188. * @param {boolean} bool
  189. * Whether to skip this operation.
  190. * @param {unknown} actual
  191. * Actual value.
  192. * @param {unknown} expected
  193. * Expected value.
  194. * @param {string} operator
  195. * Operator.
  196. * @param {string} defaultMessage
  197. * Default message for operation.
  198. * @param {Error | string | null | undefined} userMessage
  199. * User-provided message.
  200. * @returns {asserts bool}
  201. * Nothing; throws when falsey.
  202. */
  203. // eslint-disable-next-line max-params
  204. function assert(bool, actual, expected, operator, defaultMessage, userMessage) {
  205. if (!bool) {
  206. throw userMessage instanceof Error
  207. ? userMessage
  208. : new AssertionError(
  209. userMessage || defaultMessage,
  210. actual,
  211. expected,
  212. operator,
  213. !userMessage
  214. )
  215. }
  216. }