Skip to content

Commit

Permalink
Fix getRegexpRange to return correct range of the node when failed to…
Browse files Browse the repository at this point in the history
… compute real range (#2857)
  • Loading branch information
saberduck authored Oct 29, 2021
1 parent 7bd622a commit c558025
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 4 deletions.
14 changes: 11 additions & 3 deletions eslint-bridge/src/utils/utils-regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -173,26 +177,30 @@ 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
return [start + 1, end + 1];
}
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}`);
Expand Down
39 changes: 38 additions & 1 deletion eslint-bridge/tests/utils/utils-regex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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*'`);
Expand Down Expand Up @@ -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 },
});
});

0 comments on commit c558025

Please sign in to comment.