regexp.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. import { BSONValue } from './bson_value';
  2. import { BSONError } from './error';
  3. import type { EJSONOptions } from './extended_json';
  4. import { type InspectFn, defaultInspect, getStylizeFunction } from './parser/utils';
  5. function alphabetize(str: string): string {
  6. return str.split('').sort().join('');
  7. }
  8. /** @public */
  9. export interface BSONRegExpExtendedLegacy {
  10. $regex: string | BSONRegExp;
  11. $options: string;
  12. }
  13. /** @public */
  14. export interface BSONRegExpExtended {
  15. $regularExpression: {
  16. pattern: string;
  17. options: string;
  18. };
  19. }
  20. /**
  21. * A class representation of the BSON RegExp type.
  22. * @public
  23. * @category BSONType
  24. */
  25. export class BSONRegExp extends BSONValue {
  26. get _bsontype(): 'BSONRegExp' {
  27. return 'BSONRegExp';
  28. }
  29. pattern!: string;
  30. options!: string;
  31. /**
  32. * @param pattern - The regular expression pattern to match
  33. * @param options - The regular expression options
  34. */
  35. constructor(pattern: string, options?: string) {
  36. super();
  37. this.pattern = pattern;
  38. this.options = alphabetize(options ?? '');
  39. if (this.pattern.indexOf('\x00') !== -1) {
  40. throw new BSONError(
  41. `BSON Regex patterns cannot contain null bytes, found: ${JSON.stringify(this.pattern)}`
  42. );
  43. }
  44. if (this.options.indexOf('\x00') !== -1) {
  45. throw new BSONError(
  46. `BSON Regex options cannot contain null bytes, found: ${JSON.stringify(this.options)}`
  47. );
  48. }
  49. // Validate options
  50. for (let i = 0; i < this.options.length; i++) {
  51. if (
  52. !(
  53. this.options[i] === 'i' ||
  54. this.options[i] === 'm' ||
  55. this.options[i] === 'x' ||
  56. this.options[i] === 'l' ||
  57. this.options[i] === 's' ||
  58. this.options[i] === 'u'
  59. )
  60. ) {
  61. throw new BSONError(`The regular expression option [${this.options[i]}] is not supported`);
  62. }
  63. }
  64. }
  65. static parseOptions(options?: string): string {
  66. return options ? options.split('').sort().join('') : '';
  67. }
  68. /** @internal */
  69. toExtendedJSON(options?: EJSONOptions): BSONRegExpExtendedLegacy | BSONRegExpExtended {
  70. options = options || {};
  71. if (options.legacy) {
  72. return { $regex: this.pattern, $options: this.options };
  73. }
  74. return { $regularExpression: { pattern: this.pattern, options: this.options } };
  75. }
  76. /** @internal */
  77. static fromExtendedJSON(doc: BSONRegExpExtendedLegacy | BSONRegExpExtended): BSONRegExp {
  78. if ('$regex' in doc) {
  79. if (typeof doc.$regex !== 'string') {
  80. // This is for $regex query operators that have extended json values.
  81. if (doc.$regex._bsontype === 'BSONRegExp') {
  82. return doc as unknown as BSONRegExp;
  83. }
  84. } else {
  85. return new BSONRegExp(doc.$regex, BSONRegExp.parseOptions(doc.$options));
  86. }
  87. }
  88. if ('$regularExpression' in doc) {
  89. return new BSONRegExp(
  90. doc.$regularExpression.pattern,
  91. BSONRegExp.parseOptions(doc.$regularExpression.options)
  92. );
  93. }
  94. throw new BSONError(`Unexpected BSONRegExp EJSON object form: ${JSON.stringify(doc)}`);
  95. }
  96. inspect(depth?: number, options?: unknown, inspect?: InspectFn): string {
  97. const stylize = getStylizeFunction(options) ?? (v => v);
  98. inspect ??= defaultInspect;
  99. const pattern = stylize(inspect(this.pattern), 'regexp');
  100. const flags = stylize(inspect(this.options), 'regexp');
  101. return `new BSONRegExp(${pattern}, ${flags})`;
  102. }
  103. }