diff --git a/eslint-bridge/src/utils/utils-regex.ts b/eslint-bridge/src/utils/utils-regex.ts index 5daeb88f4d5..cc90c7304ae 100644 --- a/eslint-bridge/src/utils/utils-regex.ts +++ b/eslint-bridge/src/utils/utils-regex.ts @@ -143,6 +143,10 @@ export function getRegexpLocation( return loc; } +/** + * Returns the location of regexpNode relative to the node, which is regexp string or literal. If the computation + * of location fails, it returns the range of the whole node. + */ export function getRegexpRange(node: estree.Node, regexpNode: regexpp.AST.Node): AST.Range { if (isRegexLiteral(node)) { return [regexpNode.start, regexpNode.end]; @@ -173,14 +177,14 @@ export function getRegexpRange(node: estree.Node, regexpNode: regexpp.AST.Node): const startToken = regexpNode.start - 1; if (tokens[startToken] === undefined) { // fallback when something is broken - return node.range!; + return nodeRange(node); } const start = tokens[startToken].range[0]; // it's possible for node end to be outside of range, e.g. new RegExp('\n(|)') const endToken = Math.min(regexpNode.end - 2, tokens.length - 1); if (tokens[endToken] === undefined) { // fallback when something is broken - return node.range!; + return nodeRange(node); } const end = tokens[endToken].range[1]; // +1 is needed to account for string quotes @@ -188,11 +192,15 @@ export function getRegexpRange(node: estree.Node, regexpNode: regexpp.AST.Node): } if (node.type === 'TemplateLiteral') { // we don't support these properly - return node.range!; + return nodeRange(node); } throw new Error(`Expected regexp or string literal, got ${node.type}`); } +function nodeRange(node: estree.Node): [number, number] { + return [0, node.range![1] - node.range![0]]; +} + function unquote(s: string): string { if (s.charAt(0) !== "'" && s.charAt(0) !== '"') { throw new Error(`invalid string to unquote: ${s}`); diff --git a/eslint-bridge/tests/utils/utils-regex.test.ts b/eslint-bridge/tests/utils/utils-regex.test.ts index 6afb6773d2a..7ad78a20eca 100644 --- a/eslint-bridge/tests/utils/utils-regex.test.ts +++ b/eslint-bridge/tests/utils/utils-regex.test.ts @@ -20,8 +20,10 @@ import * as esprima from 'esprima'; import * as estree from 'estree'; -import { getRegexpRange } from 'utils'; +import { getRegexpLocation, getRegexpRange } from 'utils'; import * as regexpp from 'regexpp'; +import { Rule, SourceCode } from 'eslint'; +import RuleContext = Rule.RuleContext; it('should get range for regexp /s*', () => { const program = esprima.parse(`'/s*'`); @@ -50,3 +52,38 @@ it('should get range for \\ns', () => { // this fails to compute, so we return range of the whole node expect(range).toStrictEqual([0, 5]); }); + +it('should get range for template literal', () => { + const program = esprima.parse('`\\ns`', { range: true }); + const literal: estree.TemplateLiteral = program.body[0].expression; + const regexNode = regexpp.parseRegExpLiteral(new RegExp(literal.quasis[0].value.raw as string)); + const quantifier = regexNode.pattern.alternatives[0].elements[1]; + const range = getRegexpRange(literal, quantifier); + // this fails to compute, so we return range of the whole node + expect(range).toStrictEqual([0, 5]); +}); + +it('should throw for wrong node', () => { + const program = esprima.parse(`'\\ns'`, { range: true }); + expect(() => { + getRegexpRange(program, undefined); + }).toThrow('Expected regexp or string literal, got Program'); +}); + +it('should report correct range when fails to determine real range', () => { + let code = ` '\\ns'`; + const program = esprima.parse(code, { range: true, tokens: true, comment: true, loc: true }); + const literal: estree.Literal = program.body[0].expression; + const regexNode = regexpp.parseRegExpLiteral(new RegExp(literal.value as string)); + const quantifier = regexNode.pattern.alternatives[0].elements[1]; + const context = { + getSourceCode() { + return new SourceCode(code, program); + }, + }; + const range = getRegexpLocation(literal, quantifier, context as RuleContext); + expect(range).toStrictEqual({ + start: { column: 5, line: 1 }, + end: { column: 10, line: 1 }, + }); +});