[text_utils] Introduce TextRange.intersection().

This adds a new intersection() method to TextRange, which will be used
subsequentely to implement correct containment of location ranges in
https://crrev.com/c/4196922.

Bug: chromium:1409365
Change-Id: Ied32933df6de64c8b53c50fb6f44744980767385
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/4196932
Reviewed-by: Simon Zünd <szuend@chromium.org>
This commit is contained in:
Benedikt Meurer
2023-01-30 11:47:19 +00:00
parent 63d268dccc
commit 83d7dbb786
2 changed files with 88 additions and 0 deletions
+34
View File
@@ -250,6 +250,40 @@ export class TextRange {
get end(): {lineNumber: number, columnNumber: number} {
return {lineNumber: this.endLine, columnNumber: this.endColumn};
}
/**
* Checks whether this and `that` {@link TextRange} overlap and if they do, computes the
* intersection range. If they don't overlap an empty text range is returned instead (for
* which {@link #isEmpty()} yields `true`).
*
* The beginning of text ranges is considered to be includes while the end of the text
* ranges is considered exclusive for the intersection, meaning that for example intersecting
* `(0,1)-(1,4)` and `(1,4)-(1,6)` yields an empty range.
*
* @param that the other text range.
* @returns the intersection of this and `that` text range, which might be empty if their don't
* overlap.
*/
intersection(that: TextRange): TextRange {
let {startLine, startColumn} = this;
if (startLine < that.startLine) {
startLine = that.startLine;
startColumn = that.startColumn;
} else if (startLine === that.startLine) {
startColumn = Math.max(startColumn, that.startColumn);
}
let {endLine, endColumn} = this;
if (endLine > that.endLine) {
endLine = that.endLine;
endColumn = that.endColumn;
} else if (endLine === that.endLine) {
endColumn = Math.min(endColumn, that.endColumn);
}
if (startLine > endLine || (startLine === endLine && startColumn >= endColumn)) {
return new TextRange(0, 0, 0, 0);
}
return new TextRange(startLine, startColumn, endLine, endColumn);
}
}
export class SourceRange {
@@ -408,4 +408,58 @@ describe('TextRange', () => {
TextUtils.TextRange.TextRange.fromObject({startLine: 1, startColumn: 2, endLine: 3, endColumn: 4});
assert.isTrue(typeof textRange.toString() === 'string', 'toString should return a string');
});
describe('intersection', () => {
const {TextRange} = TextUtils.TextRange;
it('yields empty range for empty inputs', () => {
const range1 = new TextRange(0, 0, 0, 0);
const range2 = new TextRange(1, 4, 1, 4);
assert.isTrue(range1.intersection(range2).isEmpty(), 'intersection should be empty');
assert.isTrue(range2.intersection(range1).isEmpty(), 'intersection should be empty');
});
it('yields empty range for non-overlapping inputs', () => {
const range1 = new TextRange(1, 0, 2, 0);
const range2 = new TextRange(3, 0, 4, 0);
assert.isTrue(range1.intersection(range2).isEmpty(), 'intersection should be empty');
assert.isTrue(range2.intersection(range1).isEmpty(), 'intersection should be empty');
const range3 = new TextRange(7, 1, 8, 4);
const range4 = new TextRange(8, 4, 8, 9);
assert.isTrue(range3.intersection(range4).isEmpty(), 'intersection should be empty');
assert.isTrue(range4.intersection(range3).isEmpty(), 'intersection should be empty');
});
it('yields same range for identical inputs', () => {
const range = new TextRange(1, 2, 3, 4);
assert.deepEqual(range.intersection(range), range);
});
it('yields a point range for inputs overlapping on a single character', () => {
const range1 = new TextRange(7, 1, 7, 4);
const range2 = new TextRange(7, 3, 9, 9);
const result = new TextRange(range2.startLine, range2.startColumn, range1.endLine, range1.endColumn);
assert.deepEqual(range1.intersection(range2), result);
assert.deepEqual(range2.intersection(range1), result);
});
it('yields a copy and never the input', () => {
const range = new TextRange(8, 0, 8, 9);
const empty = new TextRange(7, 0, 7, 0);
assert.notStrictEqual(range.intersection(range), range);
assert.notStrictEqual(empty.intersection(empty), empty);
assert.notStrictEqual(empty.intersection(range), empty);
assert.notStrictEqual(empty.intersection(range), range);
assert.notStrictEqual(range.intersection(empty), empty);
assert.notStrictEqual(range.intersection(empty), range);
});
it('yields the smaller range if it is fully contained in the other', () => {
const large = new TextRange(0, 1, 10, 0);
const small = new TextRange(0, 2, 9, 25);
assert.deepEqual(large.intersection(small), small);
assert.deepEqual(small.intersection(large), small);
});
});
});