summaryrefslogtreecommitdiff
path: root/tools/eslint/lib/rules/no-control-regex.js
blob: 14981f4ab13b34c8972be435075414edbd17bb93 (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
124
125
126
127
/**
 * @fileoverview Rule to forbid control charactes from regular expressions.
 * @author Nicholas C. Zakas
 */

"use strict";

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

module.exports = {
    meta: {
        docs: {
            description: "disallow control characters in regular expressions",
            category: "Possible Errors",
            recommended: true
        },

        schema: []
    },

    create(context) {

        /**
         * Get the regex expression
         * @param {ASTNode} node node to evaluate
         * @returns {*} Regex if found else null
         * @private
         */
        function getRegExp(node) {
            if (node.value instanceof RegExp) {
                return node.value;
            }
            if (typeof node.value === "string") {

                const parent = context.getAncestors().pop();

                if ((parent.type === "NewExpression" || parent.type === "CallExpression") &&
                    parent.callee.type === "Identifier" && parent.callee.name === "RegExp"
                ) {

                    // there could be an invalid regular expression string
                    try {
                        return new RegExp(node.value);
                    } catch (ex) {
                        return null;
                    }
                }
            }

            return null;
        }


        const controlChar = /[\x00-\x1f]/g; // eslint-disable-line no-control-regex
        const consecutiveSlashes = /\\+/g;
        const consecutiveSlashesAtEnd = /\\+$/g;
        const stringControlChar = /\\x[01][0-9a-f]/ig;
        const stringControlCharWithoutSlash = /x[01][0-9a-f]/ig;

        /**
         * Return a list of the control characters in the given regex string
         * @param {string} regexStr regex as string to check
         * @returns {array} returns a list of found control characters on given string
         * @private
         */
        function getControlCharacters(regexStr) {

            // check control characters, if RegExp object used
            const controlChars = regexStr.match(controlChar) || [];

            let stringControlChars = [];

            // check substr, if regex literal used
            const subStrIndex = regexStr.search(stringControlChar);

            if (subStrIndex > -1) {

                // is it escaped, check backslash count
                const possibleEscapeCharacters = regexStr.slice(0, subStrIndex).match(consecutiveSlashesAtEnd);

                const hasControlChars = possibleEscapeCharacters === null || !(possibleEscapeCharacters[0].length % 2);

                if (hasControlChars) {
                    stringControlChars = regexStr.slice(subStrIndex, -1)
                        .split(consecutiveSlashes)
                        .filter(Boolean)
                        .map(x => {
                            const match = x.match(stringControlCharWithoutSlash) || [x];

                            return `\\${match[0]}`;
                        });
                }
            }

            return controlChars.map(x => {
                const hexCode = `0${x.charCodeAt(0).toString(16)}`.slice(-2);

                return `\\x${hexCode}`;
            }).concat(stringControlChars);
        }

        return {
            Literal(node) {
                const regex = getRegExp(node);

                if (regex) {
                    const computedValue = regex.toString();

                    const controlCharacters = getControlCharacters(computedValue);

                    if (controlCharacters.length > 0) {
                        context.report({
                            node,
                            message: "Unexpected control character(s) in regular expression: {{controlChars}}.",
                            data: {
                                controlChars: controlCharacters.join(", ")
                            }
                        });
                    }
                }
            }
        };

    }
};