matchesStringOrRegExp.mjs 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. /**
  2. * Compares a string to a second value that, if it fits a certain convention,
  3. * is converted to a regular expression before the comparison.
  4. * If it doesn't fit the convention, then two strings are compared.
  5. *
  6. * Any strings starting and ending with `/` are interpreted
  7. * as regular expressions.
  8. *
  9. * @typedef {{match: string, pattern: (string | RegExp), substring: string} | false} MatchResult
  10. *
  11. * @param {string | Array<string>} input
  12. * @param {string | RegExp | Array<string | RegExp>} comparison
  13. * @returns {MatchResult}
  14. */
  15. export default function matchesStringOrRegExp(input, comparison) {
  16. if (!Array.isArray(input)) {
  17. return testAgainstStringOrRegExpOrArray(input, comparison);
  18. }
  19. for (const inputItem of input) {
  20. const testResult = testAgainstStringOrRegExpOrArray(inputItem, comparison);
  21. if (testResult) {
  22. return testResult;
  23. }
  24. }
  25. return false;
  26. }
  27. /**
  28. * @param {string} value
  29. * @param {string | RegExp | Array<string | RegExp>} comparison
  30. * @returns {MatchResult}
  31. */
  32. function testAgainstStringOrRegExpOrArray(value, comparison) {
  33. if (!Array.isArray(comparison)) {
  34. return testAgainstStringOrRegExp(value, comparison);
  35. }
  36. for (const comparisonItem of comparison) {
  37. const testResult = testAgainstStringOrRegExp(value, comparisonItem);
  38. if (testResult) {
  39. return testResult;
  40. }
  41. }
  42. return false;
  43. }
  44. /**
  45. * @param {string} value
  46. * @param {RegExp} pattern
  47. * @returns {MatchResult}
  48. */
  49. function matchValue(value, pattern) {
  50. const match = value.match(pattern);
  51. return match ? { match: value, pattern, substring: match[0] ?? '' } : false;
  52. }
  53. /**
  54. * @param {string} value
  55. * @param {string | RegExp} comparison
  56. * @returns {MatchResult}
  57. */
  58. function testAgainstStringOrRegExp(value, comparison) {
  59. // If it's a RegExp, test directly
  60. if (comparison instanceof RegExp) {
  61. return matchValue(value, comparison);
  62. }
  63. // Check if it's RegExp in a string
  64. const regexFlag = 'i';
  65. const comparisonIsRegex =
  66. comparison.startsWith('/') &&
  67. (comparison.endsWith('/') || comparison.endsWith(`/${regexFlag}`));
  68. // If so, create a new RegExp from it
  69. if (comparisonIsRegex) {
  70. const pattern = comparison.endsWith(regexFlag)
  71. ? new RegExp(comparison.slice(1, -2), regexFlag)
  72. : new RegExp(comparison.slice(1, -1));
  73. const result = matchValue(value, pattern);
  74. if (result) {
  75. result.pattern = comparison;
  76. }
  77. return result;
  78. }
  79. // Otherwise, it's a string. Do a strict comparison
  80. return value === comparison ? { match: value, pattern: comparison, substring: value } : false;
  81. }