GetSubstitution.js
4.31 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
'use strict';
var GetIntrinsic = require('get-intrinsic');
var $TypeError = GetIntrinsic('%TypeError%');
var callBound = require('call-bind/callBound');
var regexTester = require('../helpers/regexTester');
var every = require('../helpers/every');
var $charAt = callBound('String.prototype.charAt');
var $strSlice = callBound('String.prototype.slice');
var $indexOf = callBound('String.prototype.indexOf');
var $parseInt = parseInt;
var isDigit = regexTester(/^[0-9]$/);
var inspect = require('object-inspect');
var Get = require('./Get');
var IsArray = require('./IsArray');
var IsInteger = require('./IsInteger');
var ToObject = require('./ToObject');
var ToString = require('./ToString');
var Type = require('./Type');
var canDistinguishSparseFromUndefined = 0 in [undefined]; // IE 6 - 8 have a bug where this returns false
var isStringOrHole = function (capture, index, arr) {
return Type(capture) === 'String' || (canDistinguishSparseFromUndefined ? !(index in arr) : Type(capture) === 'Undefined');
};
// http://262.ecma-international.org/9.0/#sec-getsubstitution
// eslint-disable-next-line max-statements, max-params, max-lines-per-function
module.exports = function GetSubstitution(matched, str, position, captures, namedCaptures, replacement) {
if (Type(matched) !== 'String') {
throw new $TypeError('Assertion failed: `matched` must be a String');
}
var matchLength = matched.length;
if (Type(str) !== 'String') {
throw new $TypeError('Assertion failed: `str` must be a String');
}
var stringLength = str.length;
if (!IsInteger(position) || position < 0 || position > stringLength) {
throw new $TypeError('Assertion failed: `position` must be a nonnegative integer, and less than or equal to the length of `string`, got ' + inspect(position));
}
if (!IsArray(captures) || !every(captures, isStringOrHole)) {
throw new $TypeError('Assertion failed: `captures` must be a List of Strings, got ' + inspect(captures));
}
if (Type(replacement) !== 'String') {
throw new $TypeError('Assertion failed: `replacement` must be a String');
}
var tailPos = position + matchLength;
var m = captures.length;
if (Type(namedCaptures) !== 'Undefined') {
namedCaptures = ToObject(namedCaptures); // eslint-disable-line no-param-reassign
}
var result = '';
for (var i = 0; i < replacement.length; i += 1) {
// if this is a $, and it's not the end of the replacement
var current = $charAt(replacement, i);
var isLast = (i + 1) >= replacement.length;
var nextIsLast = (i + 2) >= replacement.length;
if (current === '$' && !isLast) {
var next = $charAt(replacement, i + 1);
if (next === '$') {
result += '$';
i += 1;
} else if (next === '&') {
result += matched;
i += 1;
} else if (next === '`') {
result += position === 0 ? '' : $strSlice(str, 0, position - 1);
i += 1;
} else if (next === "'") {
result += tailPos >= stringLength ? '' : $strSlice(str, tailPos);
i += 1;
} else {
var nextNext = nextIsLast ? null : $charAt(replacement, i + 2);
if (isDigit(next) && next !== '0' && (nextIsLast || !isDigit(nextNext))) {
// $1 through $9, and not followed by a digit
var n = $parseInt(next, 10);
// if (n > m, impl-defined)
result += n <= m && Type(captures[n - 1]) === 'Undefined' ? '' : captures[n - 1];
i += 1;
} else if (isDigit(next) && (nextIsLast || isDigit(nextNext))) {
// $00 through $99
var nn = next + nextNext;
var nnI = $parseInt(nn, 10) - 1;
// if nn === '00' or nn > m, impl-defined
result += nn <= m && Type(captures[nnI]) === 'Undefined' ? '' : captures[nnI];
i += 2;
} else if (next === '<') {
// eslint-disable-next-line max-depth
if (Type(namedCaptures) === 'Undefined') {
result += '$<';
i += 2;
} else {
var endIndex = $indexOf(replacement, '>', i);
// eslint-disable-next-line max-depth
if (endIndex > -1) {
var groupName = $strSlice(replacement, i + '$<'.length, endIndex);
var capture = Get(namedCaptures, groupName);
// eslint-disable-next-line max-depth
if (Type(capture) !== 'Undefined') {
result += ToString(capture);
}
i += ('<' + groupName + '>').length;
}
}
} else {
result += '$';
}
}
} else {
// the final $, or else not a $
result += $charAt(replacement, i);
}
}
return result;
};