diff --git a/.eslintrc.yaml b/.eslintrc.yaml index 1362c9cb29..b7654381b1 100644 --- a/.eslintrc.yaml +++ b/.eslintrc.yaml @@ -136,6 +136,7 @@ rules: assert-throws-arguments: [2, { requireTwo: false }] new-with-error: [2, Error, RangeError, TypeError, SyntaxError, ReferenceError] timer-arguments: 2 + no-unescaped-regexp-dot: 2 # Global scoped method and vars globals: diff --git a/benchmark/buffers/buffer-base64-decode.js b/benchmark/buffers/buffer-base64-decode.js index 3d00e69b90..6a9002df38 100644 --- a/benchmark/buffers/buffer-base64-decode.js +++ b/benchmark/buffers/buffer-base64-decode.js @@ -9,6 +9,7 @@ const bench = common.createBenchmark(main, { function main(conf) { const n = +conf.n; const s = 'abcd'.repeat(8 << 20); + // eslint-disable-next-line no-unescaped-regexp-dot s.match(/./); // Flatten string. assert.strictEqual(s.length % 4, 0); const b = Buffer.allocUnsafe(s.length / 4 * 3); diff --git a/benchmark/child_process/child-process-exec-stdout.js b/benchmark/child_process/child-process-exec-stdout.js index 7b93aacff8..6b9bebf347 100644 --- a/benchmark/child_process/child-process-exec-stdout.js +++ b/benchmark/child_process/child-process-exec-stdout.js @@ -18,6 +18,7 @@ function main(conf) { const len = +conf.len; const msg = `"${'.'.repeat(len)}"`; + // eslint-disable-next-line no-unescaped-regexp-dot msg.match(/./); const options = {'stdio': ['ignore', 'pipe', 'ignore']}; const child = exec(`yes ${msg}`, options); diff --git a/test/addons/node-module-version/test.js b/test/addons/node-module-version/test.js index 94db088395..002e38134d 100644 --- a/test/addons/node-module-version/test.js +++ b/test/addons/node-module-version/test.js @@ -7,7 +7,7 @@ const re = new RegExp( '^Error: The module \'.+\'\n' + 'was compiled against a different Node\\.js version using\n' + 'NODE_MODULE_VERSION 42\\. This version of Node\\.js requires\n' + - `NODE_MODULE_VERSION ${process.versions.modules}. ` + + `NODE_MODULE_VERSION ${process.versions.modules}\\. ` + 'Please try re-compiling or re-installing\n' + 'the module \\(for instance, using `npm rebuild` or `npm install`\\)\\.$'); diff --git a/test/debugger/test-debugger-repl-break-in-module.js b/test/debugger/test-debugger-repl-break-in-module.js index 4fe37678fe..d428b7a6fc 100644 --- a/test/debugger/test-debugger-repl-break-in-module.js +++ b/test/debugger/test-debugger-repl-break-in-module.js @@ -54,7 +54,7 @@ repl.addTest('restart', [].concat( ], repl.handshakeLines, [ - /Restoring breakpoint mod.js:2/, + /Restoring breakpoint mod\.js:2/, /Warning: script 'mod\.js' was not loaded yet\./, /Restoring breakpoint \).*:\d+/, /Warning: script '\)[^']*' was not loaded yet\./ diff --git a/test/debugger/test-debugger-repl.js b/test/debugger/test-debugger-repl.js index f138627bb4..35bb72832b 100644 --- a/test/debugger/test-debugger-repl.js +++ b/test/debugger/test-debugger-repl.js @@ -72,7 +72,7 @@ addTest('sb("setInterval()", "!(setInterval.flag++)")', [ // Continue addTest('c', [ - /break in timers.js:\d+/, + /break in timers\.js:\d+/, /\d/, /\d/, /\d/, /\d/, /\d/ ]); diff --git a/test/inspector/test-inspector.js b/test/inspector/test-inspector.js index 1c3ef12baf..5e41864755 100644 --- a/test/inspector/test-inspector.js +++ b/test/inspector/test-inspector.js @@ -12,7 +12,7 @@ function checkListResponse(err, response) { assert.ok(response[0]['devtoolsFrontendUrl']); assert.ok( response[0]['webSocketDebuggerUrl'] - .match(/ws:\/\/127.0.0.1:\d+\/[0-9A-Fa-f]{8}-/)); + .match(/ws:\/\/127\.0\.0\.1:\d+\/[0-9A-Fa-f]{8}-/)); } function checkVersion(err, response) { diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js index fb36aee99c..c84d596232 100644 --- a/test/parallel/test-assert.js +++ b/test/parallel/test-assert.js @@ -406,7 +406,7 @@ assert.doesNotThrow(function() { assert.ifError(); }); assert.throws(() => { assert.doesNotThrow(makeBlock(thrower, Error), 'user message'); -}, /Got unwanted exception. user message/, +}, /Got unwanted exception: user message/, 'a.doesNotThrow ignores user message'); // make sure that validating using constructor really works diff --git a/test/parallel/test-buffer-alloc.js b/test/parallel/test-buffer-alloc.js index 901b335d52..59eac8266c 100644 --- a/test/parallel/test-buffer-alloc.js +++ b/test/parallel/test-buffer-alloc.js @@ -923,7 +923,7 @@ assert.throws(() => Buffer.allocUnsafe(10).copy(), /TypeError: argument should be a Buffer/); const regErrorMsg = new RegExp('First argument must be a string, Buffer, ' + - 'ArrayBuffer, Array, or array-like object.'); + 'ArrayBuffer, Array, or array-like object\\.'); assert.throws(() => Buffer.from(), regErrorMsg); assert.throws(() => Buffer.from(null), regErrorMsg); diff --git a/test/parallel/test-crypto-dh.js b/test/parallel/test-crypto-dh.js index 275d0a297f..3e664ce236 100644 --- a/test/parallel/test-crypto-dh.js +++ b/test/parallel/test-crypto-dh.js @@ -301,7 +301,7 @@ ecdh5.setPrivateKey(cafebabeKey, 'hex'); ].forEach((element) => { assert.throws(() => { ecdh5.setPrivateKey(element, 'hex'); - }, /^Error: Private key is not valid for specified curve.$/); + }, /^Error: Private key is not valid for specified curve\.$/); // Verify object state did not change. assert.strictEqual(ecdh5.getPrivateKey('hex'), cafebabeKey); }); diff --git a/test/parallel/test-debug-port-from-cmdline.js b/test/parallel/test-debug-port-from-cmdline.js index e797048476..111da8e001 100644 --- a/test/parallel/test-debug-port-from-cmdline.js +++ b/test/parallel/test-debug-port-from-cmdline.js @@ -11,8 +11,8 @@ const child = spawn(process.execPath, args, childOptions); const reDeprecationWarning = new RegExp( /^\(node:\d+\) \[DEP0062\] DeprecationWarning: /.source + - /node --debug is deprecated. /.source + - /Please use node --inspect instead.$/.source + /node --debug is deprecated\. /.source + + /Please use node --inspect instead\.$/.source ); child.stdin.write("process.send({ msg: 'childready' });\n"); @@ -47,9 +47,9 @@ function assertOutputLines() { // need a var so can swap the first two lines in following // eslint-disable-next-line no-var var expectedLines = [ - /^Starting debugger agent.$/, + /^Starting debugger agent\.$/, reDeprecationWarning, - new RegExp(`^Debugger listening on 127.0.0.1:${debugPort}$`) + new RegExp(`^Debugger listening on 127\\.0\\.0\\.1:${debugPort}$`) ]; if (os.platform() === 'win32') { diff --git a/test/parallel/test-dgram-cluster-bind-error.js b/test/parallel/test-dgram-cluster-bind-error.js index 464d80b93b..ad33328d40 100644 --- a/test/parallel/test-dgram-cluster-bind-error.js +++ b/test/parallel/test-dgram-cluster-bind-error.js @@ -17,7 +17,7 @@ if (cluster.isMaster) { const socket = dgram.createSocket('udp4'); socket.on('error', common.mustCall((err) => { - assert(/^Error: bind UNKNOWN 0.0.0.0$/.test(err.toString())); + assert(/^Error: bind UNKNOWN 0\.0\.0\.0$/.test(err.toString())); process.nextTick(common.mustCall(() => { assert.strictEqual(socket._bindState, 0); // BIND_STATE_UNBOUND socket.close(); diff --git a/test/parallel/test-fs-write-stream-throw-type-error.js b/test/parallel/test-fs-write-stream-throw-type-error.js index 940def65b3..3e1b0c0779 100644 --- a/test/parallel/test-fs-write-stream-throw-type-error.js +++ b/test/parallel/test-fs-write-stream-throw-type-error.js @@ -5,10 +5,10 @@ const fs = require('fs'); const path = require('path'); const numberError = new RegExp('^TypeError: "options" must be a string ' + - 'or an object, got number instead.$'); + 'or an object, got number instead\\.$'); const booleanError = new RegExp('^TypeError: "options" must be a string ' + - 'or an object, got boolean instead.$'); + 'or an object, got boolean instead\\.$'); const example = path.join(common.tmpDir, 'dummy'); diff --git a/test/parallel/test-http-outgoing-proto.js b/test/parallel/test-http-outgoing-proto.js index dac9363867..80240c24ec 100644 --- a/test/parallel/test-http-outgoing-proto.js +++ b/test/parallel/test-http-outgoing-proto.js @@ -33,7 +33,7 @@ assert.throws(() => { assert.throws(() => { const outgoingMessage = new OutgoingMessage(); outgoingMessage.setHeader.call({_header: 'test'}, 'test', 'value'); -}, /^Error: Can't set headers after they are sent.$/); +}, /^Error: Can't set headers after they are sent\.$/); assert.throws(() => { const outgoingMessage = new OutgoingMessage(); diff --git a/test/parallel/test-http-response-status-message.js b/test/parallel/test-http-response-status-message.js index 460fc1890f..f7ceaf2fb8 100644 --- a/test/parallel/test-http-response-status-message.js +++ b/test/parallel/test-http-response-status-message.js @@ -51,7 +51,7 @@ testCases.findByPath = function(path) { const server = net.createServer(function(connection) { connection.on('data', function(data) { - const path = data.toString().match(/GET (.*) HTTP.1.1/)[1]; + const path = data.toString().match(/GET (.*) HTTP\/1\.1/)[1]; const testCase = testCases.findByPath(path); connection.write(testCase.response); diff --git a/test/parallel/test-https-agent-create-connection.js b/test/parallel/test-https-agent-create-connection.js index 6bd86fe166..7bb79567cb 100644 --- a/test/parallel/test-https-agent-create-connection.js +++ b/test/parallel/test-https-agent-create-connection.js @@ -18,7 +18,7 @@ const options = { cert: fs.readFileSync(common.fixturesDir + '/keys/agent1-cert.pem'), }; -const expectedHeader = /^HTTP\/1.1 200 OK/; +const expectedHeader = /^HTTP\/1\.1 200 OK/; const expectedBody = /hello world\n/; const expectCertError = /^Error: unable to verify the first certificate$/; diff --git a/test/parallel/test-internal-errors.js b/test/parallel/test-internal-errors.js index 014a0a7a03..27cec48b5b 100644 --- a/test/parallel/test-internal-errors.js +++ b/test/parallel/test-internal-errors.js @@ -41,11 +41,11 @@ assert.strictEqual(err5.code, 'TEST_ERROR_1'); assert.throws( () => new errors.Error('TEST_FOO_KEY'), - /^AssertionError: An invalid error message key was used: TEST_FOO_KEY.$/); + /^AssertionError: An invalid error message key was used: TEST_FOO_KEY\.$/); // Calling it twice yields same result (using the key does not create it) assert.throws( () => new errors.Error('TEST_FOO_KEY'), - /^AssertionError: An invalid error message key was used: TEST_FOO_KEY.$/); + /^AssertionError: An invalid error message key was used: TEST_FOO_KEY\.$/); assert.throws( () => new errors.Error(1), /^AssertionError: 'number' === 'string'$/); diff --git a/test/parallel/test-internal-util-assertCrypto.js b/test/parallel/test-internal-util-assertCrypto.js index 3a7acdb65c..9d1c923255 100644 --- a/test/parallel/test-internal-util-assertCrypto.js +++ b/test/parallel/test-internal-util-assertCrypto.js @@ -5,8 +5,10 @@ const assert = require('assert'); const util = require('internal/util'); if (!process.versions.openssl) { - assert.throws(() => util.assertCrypto(), - /^Error: Node.js is not compiled with openssl crypto support$/); + assert.throws( + () => util.assertCrypto(), + /^Error: Node\.js is not compiled with openssl crypto support$/ + ); } else { assert.doesNotThrow(() => util.assertCrypto()); } diff --git a/test/parallel/test-path-parse-format.js b/test/parallel/test-path-parse-format.js index c67c3726cc..d500180943 100644 --- a/test/parallel/test-path-parse-format.js +++ b/test/parallel/test-path-parse-format.js @@ -90,15 +90,15 @@ const unixSpecialCaseFormatTests = [ const errors = [ {method: 'parse', input: [null], - message: /^TypeError: Path must be a string. Received null$/}, + message: /^TypeError: Path must be a string\. Received null$/}, {method: 'parse', input: [{}], - message: /^TypeError: Path must be a string. Received {}$/}, + message: /^TypeError: Path must be a string\. Received {}$/}, {method: 'parse', input: [true], - message: /^TypeError: Path must be a string. Received true$/}, + message: /^TypeError: Path must be a string\. Received true$/}, {method: 'parse', input: [1], - message: /^TypeError: Path must be a string. Received 1$/}, + message: /^TypeError: Path must be a string\. Received 1$/}, {method: 'parse', input: [], - message: /^TypeError: Path must be a string. Received undefined$/}, + message: /^TypeError: Path must be a string\. Received undefined$/}, {method: 'format', input: [null], message: /^TypeError: Parameter "pathObject" must be an object, not object$/}, diff --git a/test/parallel/test-process-hrtime.js b/test/parallel/test-process-hrtime.js index 895a37b9a4..6e23284f42 100644 --- a/test/parallel/test-process-hrtime.js +++ b/test/parallel/test-process-hrtime.js @@ -35,16 +35,16 @@ validateTuple(process.hrtime(tuple)); // test that only an Array may be passed to process.hrtime() assert.throws(() => { process.hrtime(1); -}, /^TypeError: process.hrtime\(\) only accepts an Array tuple$/); +}, /^TypeError: process\.hrtime\(\) only accepts an Array tuple$/); assert.throws(() => { process.hrtime([]); -}, /^TypeError: process.hrtime\(\) only accepts an Array tuple$/); +}, /^TypeError: process\.hrtime\(\) only accepts an Array tuple$/); assert.throws(() => { process.hrtime([1]); -}, /^TypeError: process.hrtime\(\) only accepts an Array tuple$/); +}, /^TypeError: process\.hrtime\(\) only accepts an Array tuple$/); assert.throws(() => { process.hrtime([1, 2, 3]); -}, /^TypeError: process.hrtime\(\) only accepts an Array tuple$/); +}, /^TypeError: process\.hrtime\(\) only accepts an Array tuple$/); function validateTuple(tuple) { assert(Array.isArray(tuple)); diff --git a/test/parallel/test-querystring.js b/test/parallel/test-querystring.js index 1f1fe304c1..4218589226 100644 --- a/test/parallel/test-querystring.js +++ b/test/parallel/test-querystring.js @@ -110,7 +110,9 @@ const qsColonTestCases = [ const extendedFunction = function() {}; extendedFunction.prototype = {a: 'b'}; const qsWeirdObjects = [ + // eslint-disable-next-line no-unescaped-regexp-dot [{regexp: /./g}, 'regexp=', {'regexp': ''}], + // eslint-disable-next-line no-unescaped-regexp-dot [{regexp: new RegExp('.', 'g')}, 'regexp=', {'regexp': ''}], [{fn: function() {}}, 'fn=', {'fn': ''}], [{fn: new Function('')}, 'fn=', {'fn': ''}], diff --git a/test/parallel/test-readline-undefined-columns.js b/test/parallel/test-readline-undefined-columns.js index 14895ac637..40fe2c16d6 100644 --- a/test/parallel/test-readline-undefined-columns.js +++ b/test/parallel/test-readline-undefined-columns.js @@ -35,7 +35,7 @@ oStream.on('end', common.mustCall(() => { iStream.write('process.s\t'); -assert(/process.std\b/.test(output)); // Completion works. +assert(/process\.std\b/.test(output)); // Completion works. assert(!/stdout/.test(output)); // Completion doesn’t show all results yet. iStream.write('\t'); diff --git a/test/parallel/test-repl-definecommand.js b/test/parallel/test-repl-definecommand.js index 07732d534e..f879043b94 100644 --- a/test/parallel/test-repl-definecommand.js +++ b/test/parallel/test-repl-definecommand.js @@ -35,8 +35,9 @@ r.defineCommand('say2', function() { }); inputStream.write('.help\n'); -assert(/\n.say1 help for say1\n/.test(output), 'help for say1 not present'); -assert(/\n.say2\n/.test(output), 'help for say2 not present'); +assert(/\n\.say1 help for say1\n/.test(output), + 'help for say1 not present'); +assert(/\n\.say2\n/.test(output), 'help for say2 not present'); inputStream.write('.say1 node developer\n'); assert(/> hello node developer/.test(output), 'say1 outputted incorrectly'); inputStream.write('.say2 node developer\n'); diff --git a/test/parallel/test-repl-harmony.js b/test/parallel/test-repl-harmony.js index 624fceaa5f..553eca844f 100644 --- a/test/parallel/test-repl-harmony.js +++ b/test/parallel/test-repl-harmony.js @@ -28,7 +28,7 @@ const args = ['-i']; const child = spawn(process.execPath, args); const input = '(function(){"use strict"; const y=1;y=2})()\n'; -const expectOut = /^> TypeError: Assignment to constant variable.\n/; +const expectOut = /^> TypeError: Assignment to constant variable\.\n/; child.stderr.setEncoding('utf8'); child.stderr.on('data', function(c) { diff --git a/test/parallel/test-repl-unexpected-token-recoverable.js b/test/parallel/test-repl-unexpected-token-recoverable.js index 0f783d3dca..652c17d8f8 100644 --- a/test/parallel/test-repl-unexpected-token-recoverable.js +++ b/test/parallel/test-repl-unexpected-token-recoverable.js @@ -12,7 +12,7 @@ const child = spawn(process.execPath, args); const input = 'var foo = "bar\\\nbaz"'; // Match '...' as well since it marks a multi-line statement -const expectOut = /^> ... undefined\n/; +const expectOut = /^> \.\.\. undefined\n/; child.stderr.setEncoding('utf8'); child.stderr.on('data', function(c) { diff --git a/test/parallel/test-repl.js b/test/parallel/test-repl.js index b6de198569..fb0fca7bd9 100644 --- a/test/parallel/test-repl.js +++ b/test/parallel/test-repl.js @@ -212,7 +212,7 @@ function error_test() { { client: client_unix, send: '(function() { "use strict"; if (true) function f() { } })()', - expect: /\bSyntaxError: In strict mode code, functions can only be declared at top level or inside a block./ // eslint-disable-line max-len + expect: /\bSyntaxError: In strict mode code, functions can only be declared at top level or inside a block\./ // eslint-disable-line max-len }, // Named functions can be used: { client: client_unix, send: 'function blah() { return 1; }', diff --git a/test/parallel/test-require-json.js b/test/parallel/test-require-json.js index 10c8694da4..2ab86f0fb9 100644 --- a/test/parallel/test-require-json.js +++ b/test/parallel/test-require-json.js @@ -27,7 +27,7 @@ const assert = require('assert'); try { require(path.join(common.fixturesDir, 'invalid.json')); } catch (err) { - const re = /test[/\\]fixtures[/\\]invalid.json: Unexpected string/; + const re = /test[/\\]fixtures[/\\]invalid\.json: Unexpected string/; const i = err.message.match(re); assert.notStrictEqual(null, i, 'require() json error should include path'); } diff --git a/test/parallel/test-stream-unshift-read-race.js b/test/parallel/test-stream-unshift-read-race.js index d5b46576f9..e04de5392a 100644 --- a/test/parallel/test-stream-unshift-read-race.js +++ b/test/parallel/test-stream-unshift-read-race.js @@ -70,7 +70,7 @@ r._read = function(n) { function pushError() { assert.throws(function() { r.push(Buffer.allocUnsafe(1)); - }, /^Error: stream.push\(\) after EOF$/); + }, /^Error: stream\.push\(\) after EOF$/); } @@ -84,7 +84,7 @@ w._write = function(chunk, encoding, cb) { r.on('end', common.mustCall(function() { assert.throws(function() { r.unshift(Buffer.allocUnsafe(1)); - }, /^Error: stream.unshift\(\) after end event$/); + }, /^Error: stream\.unshift\(\) after end event$/); w.end(); })); diff --git a/test/parallel/test-vm-cached-data.js b/test/parallel/test-vm-cached-data.js index a09a41fe98..f18bf9f540 100644 --- a/test/parallel/test-vm-cached-data.js +++ b/test/parallel/test-vm-cached-data.js @@ -89,4 +89,4 @@ assert.throws(() => { new vm.Script('function abc() {}', { cachedData: 'ohai' }); -}, /^TypeError: options.cachedData must be a Buffer instance$/); +}, /^TypeError: options\.cachedData must be a Buffer instance$/); diff --git a/test/parallel/test-vm-context.js b/test/parallel/test-vm-context.js index 171d5d7e3d..6f676034c3 100644 --- a/test/parallel/test-vm-context.js +++ b/test/parallel/test-vm-context.js @@ -90,7 +90,7 @@ assert.throws(function() { columnOffset: 123 }); }, function(err) { - return /expected-filename.js:33:130/.test(err.stack); + return /expected-filename\.js:33:130/.test(err.stack); }, 'Expected appearance of proper offset in Error stack'); // https://github.com/nodejs/node/issues/6158 diff --git a/test/parallel/test-vm-timeout.js b/test/parallel/test-vm-timeout.js index 19ccd2cee5..939f15f9fd 100644 --- a/test/parallel/test-vm-timeout.js +++ b/test/parallel/test-vm-timeout.js @@ -52,7 +52,7 @@ assert.throws(function() { }; vm.runInNewContext('runInVM(10)', context, { timeout: 10000 }); throw new Error('Test 5 failed'); -}, /Script execution timed out./); +}, /Script execution timed out\./); // Test 6: Nested vm timeouts, outer timeout is shorter and fires first. assert.throws(function() { @@ -63,7 +63,7 @@ assert.throws(function() { }; vm.runInNewContext('runInVM(10000)', context, { timeout: 100 }); throw new Error('Test 6 failed'); -}, /Script execution timed out./); +}, /Script execution timed out\./); // Test 7: Nested vm timeouts, inner script throws an error. assert.throws(function() { diff --git a/test/sequential/test-deprecation-flags.js b/test/sequential/test-deprecation-flags.js index ae5de81439..156156298f 100644 --- a/test/sequential/test-deprecation-flags.js +++ b/test/sequential/test-deprecation-flags.js @@ -61,7 +61,9 @@ execFile(node, traceDep, function(er, stdout, stderr) { assert.strictEqual(stdout, ''); const stack = stderr.trim().split('\n'); // just check the top and bottom. - assert(/util.debug is deprecated. Use console.error instead./.test(stack[1])); + assert( + /util\.debug is deprecated\. Use console\.error instead\./.test(stack[1]) + ); assert(/DEBUG: This is deprecated/.test(stack[0])); console.log('trace ok'); }); diff --git a/test/sequential/test-module-loading.js b/test/sequential/test-module-loading.js index 1894bc2016..3148fe837a 100644 --- a/test/sequential/test-module-loading.js +++ b/test/sequential/test-module-loading.js @@ -121,7 +121,7 @@ const my_path = require('../fixtures/path'); assert.ok(my_path.path_func instanceof Function); // this one does not exist and should throw assert.throws(function() { require('./utils'); }, - /^Error: Cannot find module '.\/utils'$/); + /^Error: Cannot find module '\.\/utils'$/); let errorThrown = false; try { @@ -318,5 +318,5 @@ assert.strictEqual(42, require('../fixtures/utf8-bom.json')); assert.throws(function() { require('../fixtures/test-error-first-line-offset.js'); }, function(err) { - return /test-error-first-line-offset.js:1:/.test(err.stack); + return /test-error-first-line-offset\.js:1:/.test(err.stack); }, 'Expected appearance of proper offset in Error stack'); diff --git a/test/sequential/test-process-warnings.js b/test/sequential/test-process-warnings.js index d4894f8bff..83952a2933 100644 --- a/test/sequential/test-process-warnings.js +++ b/test/sequential/test-process-warnings.js @@ -29,5 +29,5 @@ execFile(node, traceWarn, function(er, stdout, stderr) { assert.strictEqual(er, null); assert.strictEqual(stdout, ''); assert(/^\(.+\)\sWarning: a bad practice warning/.test(stderr)); - assert(/at Object\.\s\(.+warnings.js:3:9\)/.test(stderr)); + assert(/at Object\.\s\(.+warnings\.js:3:9\)/.test(stderr)); }); diff --git a/tools/eslint-rules/no-unescaped-regexp-dot.js b/tools/eslint-rules/no-unescaped-regexp-dot.js new file mode 100644 index 0000000000..cf542a3d70 --- /dev/null +++ b/tools/eslint-rules/no-unescaped-regexp-dot.js @@ -0,0 +1,157 @@ +/** + * @fileoverview Look for unescaped "literal" dots in regular expressions + * @author Brian White + */ +'use strict'; + +const path = require('path'); +const utilsPath = path.join(__dirname, '..', 'eslint', 'lib', 'ast-utils.js'); +const astUtils = require(utilsPath); +const getLocationFromRangeIndex = astUtils.getLocationFromRangeIndex; +const getRangeIndexFromLocation = astUtils.getRangeIndexFromLocation; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +module.exports = function(context) { + const sourceCode = context.getSourceCode(); + const regexpStack = []; + var regexpBuffer = []; + var inRegExp = false; + + var getLocFromIndex; + if (typeof sourceCode.getLocFromIndex === 'function') { + getLocFromIndex = function(index) { + return sourceCode.getLocFromIndex(index); + }; + } else { + getLocFromIndex = function(index) { + return getLocationFromRangeIndex(sourceCode, index); + }; + } + + var getIndexFromLoc; + if (typeof sourceCode.getIndexFromLoc === 'function') { + getIndexFromLoc = function(loc) { + return sourceCode.getIndexFromLoc(loc); + }; + } else { + getIndexFromLoc = function(loc) { + return getRangeIndexFromLocation(sourceCode, loc); + }; + } + + function report(node, startOffset) { + context.report({ + node, + loc: getLocFromIndex(getIndexFromLoc(node.loc.start) + startOffset), + message: 'Unescaped dot character in regular expression' + }); + } + + const allowedModifiers = ['+', '*', '?', '{']; + function checkRegExp(nodes) { + var escaping = false; + var inCharClass = false; + for (var n = 0; n < nodes.length; ++n) { + const pair = nodes[n]; + const node = pair[0]; + const str = pair[1]; + for (var i = 0; i < str.length; ++i) { + switch (str[i]) { + case '[': + if (!escaping) + inCharClass = true; + else + escaping = false; + break; + case ']': + if (!escaping) { + if (inCharClass) + inCharClass = false; + } else { + escaping = false; + } + break; + case '\\': + escaping = !escaping; + break; + case '.': + if (!escaping) { + if (!inCharClass && + ((i + 1) === str.length || + allowedModifiers.indexOf(str[i + 1]) === -1)) { + report(node, i); + } + } else { + escaping = false; + } + break; + default: + if (escaping) + escaping = false; + } + } + } + } + + function checkRegExpStart(node) { + if (node.callee && node.callee.name === 'RegExp') { + if (inRegExp) { + regexpStack.push(regexpBuffer); + regexpBuffer = []; + } + inRegExp = true; + } + } + + function checkRegExpEnd(node) { + if (node.callee && node.callee.name === 'RegExp') { + checkRegExp(regexpBuffer); + if (regexpStack.length) { + regexpBuffer = regexpStack.pop(); + } else { + inRegExp = false; + regexpBuffer = []; + } + } + } + + function checkLiteral(node) { + const isTemplate = (node.type === 'TemplateLiteral' && node.quasis && + node.quasis.length); + if (inRegExp && + (isTemplate || (typeof node.value === 'string' && node.value.length))) { + var p = node.parent; + while (p && p.type === 'BinaryExpression') { + p = p.parent; + } + if (p && (p.type === 'NewExpression' || p.type === 'CallExpression') && + p.callee && p.callee.type === 'Identifier' && + p.callee.name === 'RegExp') { + if (isTemplate) { + const quasis = node.quasis; + for (var i = 0; i < quasis.length; ++i) { + const el = quasis[i]; + if (el.type === 'TemplateElement' && el.value && el.value.cooked) + regexpBuffer.push([el, el.value.cooked]); + } + } else { + regexpBuffer.push([node, node.value]); + } + } + } else if (node.regex) { + checkRegExp([[node, node.regex.pattern]]); + } + } + + return { + TemplateLiteral: checkLiteral, + Literal: checkLiteral, + CallExpression: checkRegExpStart, + NewExpression: checkRegExpStart, + 'CallExpression:exit': checkRegExpEnd, + 'NewExpression:exit': checkRegExpEnd + }; +};