• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Rule to forbid control characters from regular expressions.
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
7
8const RegExpValidator = require("regexpp").RegExpValidator;
9const collector = new (class {
10    constructor() {
11        this.ecmaVersion = 2018;
12        this._source = "";
13        this._controlChars = [];
14        this._validator = new RegExpValidator(this);
15    }
16
17    onPatternEnter() {
18        this._controlChars = [];
19    }
20
21    onCharacter(start, end, cp) {
22        if (cp >= 0x00 &&
23            cp <= 0x1F &&
24            (
25                this._source.codePointAt(start) === cp ||
26                this._source.slice(start, end).startsWith("\\x") ||
27                this._source.slice(start, end).startsWith("\\u")
28            )
29        ) {
30            this._controlChars.push(`\\x${`0${cp.toString(16)}`.slice(-2)}`);
31        }
32    }
33
34    collectControlChars(regexpStr) {
35        try {
36            this._source = regexpStr;
37            this._validator.validatePattern(regexpStr); // Call onCharacter hook
38        } catch {
39
40            // Ignore syntax errors in RegExp.
41        }
42        return this._controlChars;
43    }
44})();
45
46//------------------------------------------------------------------------------
47// Rule Definition
48//------------------------------------------------------------------------------
49
50module.exports = {
51    meta: {
52        type: "problem",
53
54        docs: {
55            description: "disallow control characters in regular expressions",
56            category: "Possible Errors",
57            recommended: true,
58            url: "https://eslint.org/docs/rules/no-control-regex"
59        },
60
61        schema: [],
62
63        messages: {
64            unexpected: "Unexpected control character(s) in regular expression: {{controlChars}}."
65        }
66    },
67
68    create(context) {
69
70        /**
71         * Get the regex expression
72         * @param {ASTNode} node node to evaluate
73         * @returns {RegExp|null} Regex if found else null
74         * @private
75         */
76        function getRegExpPattern(node) {
77            if (node.regex) {
78                return node.regex.pattern;
79            }
80            if (typeof node.value === "string" &&
81                (node.parent.type === "NewExpression" || node.parent.type === "CallExpression") &&
82                node.parent.callee.type === "Identifier" &&
83                node.parent.callee.name === "RegExp" &&
84                node.parent.arguments[0] === node
85            ) {
86                return node.value;
87            }
88
89            return null;
90        }
91
92        return {
93            Literal(node) {
94                const pattern = getRegExpPattern(node);
95
96                if (pattern) {
97                    const controlCharacters = collector.collectControlChars(pattern);
98
99                    if (controlCharacters.length > 0) {
100                        context.report({
101                            node,
102                            messageId: "unexpected",
103                            data: {
104                                controlChars: controlCharacters.join(", ")
105                            }
106                        });
107                    }
108                }
109            }
110        };
111
112    }
113};
114