esnext.string.dedent.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. 'use strict';
  2. var FREEZING = require('../internals/freezing');
  3. var $ = require('../internals/export');
  4. var shared = require('../internals/shared');
  5. var getBuiltIn = require('../internals/get-built-in');
  6. var makeBuiltIn = require('../internals/make-built-in');
  7. var uncurryThis = require('../internals/function-uncurry-this');
  8. var apply = require('../internals/function-apply');
  9. var anObject = require('../internals/an-object');
  10. var toObject = require('../internals/to-object');
  11. var isCallable = require('../internals/is-callable');
  12. var lengthOfArrayLike = require('../internals/length-of-array-like');
  13. var defineProperty = require('../internals/object-define-property').f;
  14. var createArrayFromList = require('../internals/array-slice-simple');
  15. var cooked = require('../internals/string-cooked');
  16. var parse = require('../internals/string-parse');
  17. var whitespaces = require('../internals/whitespaces');
  18. var WeakMap = getBuiltIn('WeakMap');
  19. var globalDedentRegistry = shared('GlobalDedentRegistry', new WeakMap());
  20. /* eslint-disable no-self-assign -- prototype methods protection */
  21. globalDedentRegistry.has = globalDedentRegistry.has;
  22. globalDedentRegistry.get = globalDedentRegistry.get;
  23. globalDedentRegistry.set = globalDedentRegistry.set;
  24. /* eslint-enable no-self-assign -- prototype methods protection */
  25. var $Array = Array;
  26. var $TypeError = TypeError;
  27. // eslint-disable-next-line es/no-object-freeze -- safe
  28. var freeze = Object.freeze || Object;
  29. // eslint-disable-next-line es/no-object-isfrozen -- safe
  30. var isFrozen = Object.isFrozen;
  31. var min = Math.min;
  32. var charAt = uncurryThis(''.charAt);
  33. var stringSlice = uncurryThis(''.slice);
  34. var split = uncurryThis(''.split);
  35. var exec = uncurryThis(/./.exec);
  36. var NEW_LINE = /([\n\u2028\u2029]|\r\n?)/g;
  37. var LEADING_WHITESPACE = RegExp('^[' + whitespaces + ']*');
  38. var NON_WHITESPACE = RegExp('[^' + whitespaces + ']');
  39. var INVALID_TAG = 'Invalid tag';
  40. var INVALID_OPENING_LINE = 'Invalid opening line';
  41. var INVALID_CLOSING_LINE = 'Invalid closing line';
  42. var dedentTemplateStringsArray = function (template) {
  43. var rawInput = template.raw;
  44. // https://github.com/tc39/proposal-string-dedent/issues/75
  45. if (FREEZING && !isFrozen(rawInput)) throw new $TypeError('Raw template should be frozen');
  46. if (globalDedentRegistry.has(rawInput)) return globalDedentRegistry.get(rawInput);
  47. var raw = dedentStringsArray(rawInput);
  48. var cookedArr = cookStrings(raw);
  49. defineProperty(cookedArr, 'raw', {
  50. value: freeze(raw)
  51. });
  52. freeze(cookedArr);
  53. globalDedentRegistry.set(rawInput, cookedArr);
  54. return cookedArr;
  55. };
  56. var dedentStringsArray = function (template) {
  57. var t = toObject(template);
  58. var length = lengthOfArrayLike(t);
  59. var blocks = $Array(length);
  60. var dedented = $Array(length);
  61. var i = 0;
  62. var lines, common, quasi, k;
  63. if (!length) throw new $TypeError(INVALID_TAG);
  64. for (; i < length; i++) {
  65. var element = t[i];
  66. if (typeof element == 'string') blocks[i] = split(element, NEW_LINE);
  67. else throw new $TypeError(INVALID_TAG);
  68. }
  69. for (i = 0; i < length; i++) {
  70. var lastSplit = i + 1 === length;
  71. lines = blocks[i];
  72. if (i === 0) {
  73. if (lines.length === 1 || lines[0].length > 0) {
  74. throw new $TypeError(INVALID_OPENING_LINE);
  75. }
  76. lines[1] = '';
  77. }
  78. if (lastSplit) {
  79. if (lines.length === 1 || exec(NON_WHITESPACE, lines[lines.length - 1])) {
  80. throw new $TypeError(INVALID_CLOSING_LINE);
  81. }
  82. lines[lines.length - 2] = '';
  83. lines[lines.length - 1] = '';
  84. }
  85. for (var j = 2; j < lines.length; j += 2) {
  86. var text = lines[j];
  87. var lineContainsTemplateExpression = j + 1 === lines.length && !lastSplit;
  88. var leading = exec(LEADING_WHITESPACE, text)[0];
  89. if (!lineContainsTemplateExpression && leading.length === text.length) {
  90. lines[j] = '';
  91. continue;
  92. }
  93. common = commonLeadingIndentation(leading, common);
  94. }
  95. }
  96. var count = common ? common.length : 0;
  97. for (i = 0; i < length; i++) {
  98. lines = blocks[i];
  99. quasi = lines[0];
  100. k = 1;
  101. for (; k < lines.length; k += 2) {
  102. quasi += lines[k] + stringSlice(lines[k + 1], count);
  103. }
  104. dedented[i] = quasi;
  105. }
  106. return dedented;
  107. };
  108. var commonLeadingIndentation = function (a, b) {
  109. if (b === undefined || a === b) return a;
  110. var i = 0;
  111. for (var len = min(a.length, b.length); i < len; i++) {
  112. if (charAt(a, i) !== charAt(b, i)) break;
  113. }
  114. return stringSlice(a, 0, i);
  115. };
  116. var cookStrings = function (raw) {
  117. var i = 0;
  118. var length = raw.length;
  119. var result = $Array(length);
  120. for (; i < length; i++) {
  121. result[i] = parse(raw[i]);
  122. } return result;
  123. };
  124. var makeDedentTag = function (tag) {
  125. return makeBuiltIn(function (template /* , ...substitutions */) {
  126. var args = createArrayFromList(arguments);
  127. args[0] = dedentTemplateStringsArray(anObject(template));
  128. return apply(tag, this, args);
  129. }, '');
  130. };
  131. var cookedDedentTag = makeDedentTag(cooked);
  132. // `String.dedent` method
  133. // https://github.com/tc39/proposal-string-dedent
  134. $({ target: 'String', stat: true, forced: true }, {
  135. dedent: function dedent(templateOrFn /* , ...substitutions */) {
  136. anObject(templateOrFn);
  137. if (isCallable(templateOrFn)) return makeDedentTag(templateOrFn);
  138. return apply(cookedDedentTag, this, arguments);
  139. }
  140. });