Implement `esccharopen` Dangling backslash lint rule
MODIFY
packages/@romejs/diagnostics/categories.ts
packages/@romejs/diagnostics/categories.ts
@@ -30,6 +30,7 @@ export type DiagnosticCategory =
| 'lint/noAsyncPromiseExecutor'
| 'lint/noCompareNegZero'
| 'lint/noCondAssign'
+ | 'lint/noDanglingBackslash'
| 'lint/noDebugger'
| 'lint/noDeleteVars'
| 'lint/noDupeArgs'

MODIFY
packages/@romejs/js-compiler/transforms/lint/index.ts
packages/@romejs/js-compiler/transforms/lint/index.ts
@@ -14,6 +14,7 @@ import getterReturn from './getterReturn';
import noAsyncPromiseExecutor from './noAsyncPromiseExecutor';
import noCompareNegZero from './noCompareNegZero';
import noCondAssign from './noCondAssign';
+import noDanglingBackslashInRegularExpressions from './noDanglingBackslashInRegularExpressions';
import noDebugger from './noDebugger';
import noDeleteVars from './noDeleteVars';
import noDupeArgs from './noDupeArgs';
@@ -45,6 +46,7 @@ export const lintTransforms = [
noAsyncPromiseExecutor,
noCompareNegZero,
noCondAssign,
+ noDanglingBackslashInRegularExpressions,
noDebugger,
noDeleteVars,
noDupeArgs,

ADD
packages/@romejs/js-compiler/transforms/lint/noDanglingBackslashInRegularExpressions.test.md
+# `noDanglingBackslashInRegularExpressions.test.ts`
+
+**DO NOT MODIFY**. This file has been autogenerated. Run `rome test packages/@romejs/js-compiler/transforms/lint/noDanglingBackslashInRegularExpressions.test.ts --update-snapshots` to update.
+
+## `Dangling backslash in regex`
+
+### `0`
+
+```javascript
+Object {
+ src: 'let foo = /\\/;foo;'
+ suppressions: Array []
+ diagnostics: Array [
+ Object {
+ category: 'lint/noDanglingBackslash'
+ filename: 'unknown'
+ language: 'js'
+ message: 'Found a dangling backslash in the regular expression'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'lint'}]
+ end: Object {
+ column: 12
+ index: 13
+ line: 1
+ }
+ start: Object {
+ column: 11
+ index: 11
+ line: 1
+ }
+ }
+ ]
+}
+```
+
+### `1`
+
+```javascript
+Object {
+ src: 'let foo = /\\\\\\/;foo;'
+ suppressions: Array []
+ diagnostics: Array [
+ Object {
+ category: 'lint/noDanglingBackslash'
+ filename: 'unknown'
+ language: 'js'
+ message: 'Found a dangling backslash in the regular expression'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'lint'}]
+ end: Object {
+ column: 14
+ index: 15
+ line: 1
+ }
+ start: Object {
+ column: 13
+ index: 13
+ line: 1
+ }
+ }
+ ]
+}
+```
+
+### `2`
+
+```javascript
+Object {
+ src: 'let foo = /\\/\\/;\\/;foo;'
+ suppressions: Array []
+ diagnostics: Array [
+ Object {
+ advice: undefined
+ category: 'parse/js'
+ filename: 'unknown'
+ message: 'Expecting Unicode escape sequence \\uXXXX'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'js-parser'}]
+ end: Object {
+ column: 17
+ index: 17
+ line: 1
+ }
+ start: Object {
+ column: 17
+ index: 17
+ line: 1
+ }
+ }
+ Object {
+ category: 'lint/noDanglingBackslash'
+ filename: 'unknown'
+ language: 'js'
+ message: 'Found a dangling backslash in the regular expression'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'lint'}]
+ end: Object {
+ column: 14
+ index: 15
+ line: 1
+ }
+ start: Object {
+ column: 13
+ index: 13
+ line: 1
+ }
+ }
+ Object {
+ category: 'lint/undeclaredVariables'
+ filename: 'unknown'
+ language: 'js'
+ message: 'Undeclared variable <emphasis>\0</emphasis>'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'lint'}]
+ end: Object {
+ column: 18
+ index: 18
+ line: 1
+ }
+ start: Object {
+ column: 16
+ index: 16
+ line: 1
+ }
+ }
+ ]
+}
+```
+
+### `3`
+
+```javascript
+Object {
+ src: 'let foo = /\\/\\/;\\/\n foo;'
+ suppressions: Array []
+ diagnostics: Array [
+ Object {
+ advice: undefined
+ category: 'parse/js'
+ filename: 'unknown'
+ message: 'Expecting Unicode escape sequence \\uXXXX'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'js-parser'}]
+ end: Object {
+ column: 17
+ index: 17
+ line: 1
+ }
+ start: Object {
+ column: 17
+ index: 17
+ line: 1
+ }
+ }
+ Object {
+ category: 'lint/noDanglingBackslash'
+ filename: 'unknown'
+ language: 'js'
+ message: 'Found a dangling backslash in the regular expression'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'lint'}]
+ end: Object {
+ column: 14
+ index: 15
+ line: 1
+ }
+ start: Object {
+ column: 13
+ index: 13
+ line: 1
+ }
+ }
+ Object {
+ category: 'lint/undeclaredVariables'
+ filename: 'unknown'
+ language: 'js'
+ message: 'Undeclared variable <emphasis>\0</emphasis>'
+ mtime: undefined
+ sourceType: 'module'
+ origins: Array [Object {category: 'lint'}]
+ end: Object {
+ column: 18
+ index: 18
+ line: 1
+ }
+ start: Object {
+ column: 16
+ index: 16
+ line: 1
+ }
+ }
+ ]
+}
+```

ADD
packages/@romejs/js-compiler/transforms/lint/noDanglingBackslashInRegularExpressions.test.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import test from '@romejs/test';
+import {testLint} from '../../api/lint.test';
+
+test('Dangling backslash in regex', async t => {
+ const validTestCases = [
+ String.raw`let foo = /\\/;foo;`,
+ String.raw`let foo = /2\/1=2/;foo;`,
+ String.raw`let foo = /\/\//;foo;`,
+ String.raw`let foo = /\/\//
+ foo;`,
+ ];
+
+ const invalidTestCases = [
+ String.raw`let foo = /\/;foo;`,
+ String.raw`let foo = /\\\/;foo;`,
+ String.raw`let foo = /\/\/;\/;foo;`,
+ String.raw`let foo = /\/\/;\/
+ foo;`,
+ ];
+
+ for (const validTestCase of validTestCases) {
+ const {diagnostics} = await testLint(validTestCase);
+ t.is(diagnostics.length, 0);
+ }
+
+ for (const invalidTestCase of invalidTestCases) {
+ let res = await testLint(invalidTestCase);
+ t.snapshot(res);
+ }
+});

ADD
packages/@romejs/js-compiler/transforms/lint/noDanglingBackslashInRegularExpressions.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import {Path} from '@romejs/js-compiler';
+import {AnyNode} from '@romejs/js-ast';
+
+export default {
+ name: 'noDanglingBackslash',
+ enter(path: Path): AnyNode {
+ const {node, context} = path;
+
+ if (node.type === 'RegExpSubExpression') {
+ const body = node.body;
+ if (body) {
+ const last = body[body.length - 1];
+ if (last && last.type === 'RegExpCharacter' && !last.value) {
+ context.addNodeDiagnostic(last, {
+ category: 'lint/noDanglingBackslash',
+ message: 'Found a dangling backslash in the regular expression',
+ });
+ }
+ }
+ }
+ return node;
+ },
+};

MODIFY
packages/@romejs/js-parser/tokenizer/index.ts
packages/@romejs/js-parser/tokenizer/index.ts
@@ -1018,6 +1018,7 @@ export function readRegexp(parser: JSParser): void {
}
const ch = parser.input.charAt(getIndex(parser));
+ const nextCh = parser.input.charAt(getIndex(parser) + 1);
if (lineBreak.test(ch)) {
parser.addDiagnostic({
end: parser.getPositionFromIndex(parser.state.index),
@@ -1027,6 +1028,13 @@ export function readRegexp(parser: JSParser): void {
}
if (escaped) {
+ if (
+ ch === '/' &&
+ !inClass &&
+ (nextCh === ';' || lineBreak.test(nextCh))
+ ) {
+ break;
+ }
escaped = false;
} else {
if (ch === '[') {

Implement `esccharopen` Dangling backslash lint rule