summaryrefslogtreecommitdiffstats
path: root/js/src/tests/non262/RegExp/lastIndex-match-or-replace.js
blob: 8d7703c4599f9ed70bac8d56ac01daf4318dd0b7 (plain)
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
// RegExp.prototype[Symbol.match, Symbol.replace]: Test lastIndex changes for ES2017.

// RegExp-like class to test the RegExp method slow paths.
class DuckRegExp extends RegExp {
    constructor(pattern, flags) {
        return Object.create(DuckRegExp.prototype, {
            regExp: {
                value: new RegExp(pattern, flags)
            },
            lastIndex: {
                value: 0, writable: true, enumerable: false, configurable: false
            }
        });
    }

    exec(...args) {
        this.regExp.lastIndex = this.lastIndex;
        try {
            return this.regExp.exec(...args);
        } finally {
            if (this.global || this.sticky)
                this.lastIndex = this.regExp.lastIndex;
        }
    }

    get source() { return this.regExp.source; }

    get flags() { return this.regExp.flags; }
    get global() { return this.regExp.global; }
    get ignoreCase() { return this.regExp.ignoreCase; }
    get multiline() { return this.regExp.multiline; }
    get sticky() { return this.regExp.sticky; }
    get unicode() { return this.regExp.unicode; }
}

// Test various combinations of:
// - Pattern matches or doesn't match
// - Global and/or sticky flag is set.
// - lastIndex exceeds the input string length
// - lastIndex is +-0
const testCases = [
    { regExp: /a/,  lastIndex: 0, input: "a", result: 0 },
    { regExp: /a/g, lastIndex: 0, input: "a", result: 0 },
    { regExp: /a/y, lastIndex: 0, input: "a", result: 1 },

    { regExp: /a/,  lastIndex: 0, input: "b", result: 0 },
    { regExp: /a/g, lastIndex: 0, input: "b", result: 0 },
    { regExp: /a/y, lastIndex: 0, input: "b", result: 0 },

    { regExp: /a/,  lastIndex: -0, input: "a", result: -0 },
    { regExp: /a/g, lastIndex: -0, input: "a", result: 0 },
    { regExp: /a/y, lastIndex: -0, input: "a", result: 1 },

    { regExp: /a/,  lastIndex: -0, input: "b", result: -0 },
    { regExp: /a/g, lastIndex: -0, input: "b", result: 0 },
    { regExp: /a/y, lastIndex: -0, input: "b", result: 0 },

    { regExp: /a/,  lastIndex: -1, input: "a", result: -1 },
    { regExp: /a/g, lastIndex: -1, input: "a", result: 0 },
    { regExp: /a/y, lastIndex: -1, input: "a", result: 1 },

    { regExp: /a/,  lastIndex: -1, input: "b", result: -1 },
    { regExp: /a/g, lastIndex: -1, input: "b", result: 0 },
    { regExp: /a/y, lastIndex: -1, input: "b", result: 0 },

    { regExp: /a/,  lastIndex: 100, input: "a", result: 100 },
    { regExp: /a/g, lastIndex: 100, input: "a", result: 0 },
    { regExp: /a/y, lastIndex: 100, input: "a", result: 0 },
];

for (let method of [RegExp.prototype[Symbol.match], RegExp.prototype[Symbol.replace]]) {
    for (let Constructor of [RegExp, DuckRegExp]) {
        // Basic test.
        for (let {regExp, lastIndex, input, result} of testCases) {
            let re = new Constructor(regExp);
            re.lastIndex = lastIndex;
            Reflect.apply(method, re, [input]);
            assertEq(re.lastIndex, result);
        }

        // Test when lastIndex is non-writable.
        for (let {regExp, lastIndex, input} of testCases) {
            let re = new Constructor(regExp);
            Object.defineProperty(re, "lastIndex", { value: lastIndex, writable: false });
            if (re.global || re.sticky) {
                assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError);
            } else {
                Reflect.apply(method, re, [input]);
            }
            assertEq(re.lastIndex, lastIndex);
        }

        // Test when lastIndex is changed to non-writable as a side-effect.
        for (let {regExp, lastIndex, input, result} of testCases) {
            let re = new Constructor(regExp);
            let called = false;
            re.lastIndex = {
                valueOf() {
                    assertEq(called, false);
                    called = true;
                    Object.defineProperty(re, "lastIndex", { value: 9000, writable: false });
                    return lastIndex;
                }
            };
            if (re.sticky) {
                assertThrowsInstanceOf(() => Reflect.apply(method, re, [input]), TypeError);
                assertEq(called, true);
                assertEq(re.lastIndex, 9000);
            } else if (re.global) {
                Reflect.apply(method, re, [input]);
                assertEq(called, false);
                assertEq(re.lastIndex, result);
            } else {
                Reflect.apply(method, re, [input]);
                assertEq(called, true);
                assertEq(re.lastIndex, 9000);
            }
        }
    }
}

if (typeof reportCompare === "function")
    reportCompare(true, true);