| import antlr4 from "../../src/antlr4/index.node.js"; |
| import abc from "./generatedCode/abc.js"; |
| import calc from "./generatedCode/calc.js"; |
| |
| /** |
| * |
| * @param {antlr4.Lexer} lexerClass |
| * @param {string} input |
| */ |
| function getRewriter(lexerClass, input) { |
| const chars = antlr4.CharStreams.fromString(input); |
| const lexer = new lexerClass(chars); |
| const tokens = new antlr4.CommonTokenStream(lexer); |
| tokens.fill(); |
| return new antlr4.TokenStreamRewriter(tokens); |
| } |
| |
| describe("TokenStreamRewriter", () => { |
| it("inserts '0' before index 0", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(0, "0"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("0abc"); |
| }); |
| |
| it("inserts 'x' after last index", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertAfter(2, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abcx"); |
| }); |
| |
| it("inserts 'x' after the 'b' token", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| const bToken = rewriter.tokens.get(1); |
| |
| // Act |
| rewriter.insertAfter(bToken, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abxc"); |
| }); |
| |
| it("inserts 'x' at the end if the index is out of bounds", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertAfter(100, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abcx"); |
| }); |
| |
| it("inserts 'x' before the 'b' token", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| const bToken = rewriter.tokens.get(1); |
| |
| // Act |
| rewriter.insertBefore(bToken, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("axbc"); |
| }); |
| |
| it("inserts 'x' before and after middle index", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(1, "x"); |
| rewriter.insertAfter(1, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("axbxc"); |
| }); |
| |
| it("replaces the first token with an 'x'", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replaceSingle(0, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("xbc"); |
| }); |
| |
| it("replaces the last token with an 'x'", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replaceSingle(2, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abx"); |
| }); |
| |
| it("replaces the middle token with an 'x'", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replaceSingle(1, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("axc"); |
| }); |
| |
| it("calls getText() with different start/stop arguments (1 of 2)", () => { |
| // Arrange |
| const rewriter = getRewriter(calc, "x = 3 * 0;"); |
| |
| // Act |
| rewriter.replace(4, 8, "0"); // replace 3 * 0 with 0 |
| |
| // Assert |
| expect(rewriter.getTokenStream().getText()).toEqual("x = 3 * 0;"); |
| expect(rewriter.getText()).toEqual("x = 0;"); |
| expect(rewriter.getText(new antlr4.Interval(0, 9))).toEqual("x = 0;"); |
| expect(rewriter.getText(new antlr4.Interval(4, 8))).toEqual("0"); |
| }); |
| |
| it("calls getText() with different start/stop arguments (2 of 2)", () => { |
| // Arrange |
| const rewriter = getRewriter(calc, "x = 3 * 0 + 2 * 0;"); |
| |
| // Act/Assert |
| expect(rewriter.getTokenStream().getText()).toEqual("x = 3 * 0 + 2 * 0;"); |
| |
| rewriter.replace(4, 8, "0"); // replace 3 * 0 with 0 |
| |
| expect(rewriter.getText()).toEqual("x = 0 + 2 * 0;"); |
| expect(rewriter.getText(new antlr4.Interval(0, 17))).toEqual("x = 0 + 2 * 0;"); |
| expect(rewriter.getText(new antlr4.Interval(4, 8))).toEqual("0"); |
| expect(rewriter.getText(new antlr4.Interval(0, 8))).toEqual("x = 0"); |
| expect(rewriter.getText(new antlr4.Interval(12, 16))).toEqual("2 * 0"); |
| |
| rewriter.insertAfter(17, "// comment"); |
| |
| expect(rewriter.getText(new antlr4.Interval(12, 18))).toEqual("2 * 0;// comment"); |
| expect(rewriter.getText(new antlr4.Interval(0, 8))).toEqual("x = 0"); |
| }); |
| |
| it("replaces the middle index, twice", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replaceSingle(1, "x"); |
| rewriter.replaceSingle(1, "y"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("ayc"); |
| }); |
| |
| it("inserts '_' at the beginning and then replaces the middle token, twice", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(0, "_"); |
| rewriter.replaceSingle(1, "x"); |
| rewriter.replaceSingle(1, "y"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("_ayc"); |
| }); |
| |
| it("replaces, then deletes the middle index", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replaceSingle(1, "x"); |
| rewriter.delete(1); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("ac"); |
| }); |
| |
| it("throws an error when inserting into a replaced segment", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replace(0, 2, "x"); |
| rewriter.insertBefore(1, "0"); |
| |
| // Assert |
| expect(() => rewriter.getText()).toThrowError( |
| "insert op <InsertBeforeOp@[@1,1:1='b',<2>,1:1]:\"0\"> within boundaries of previous <ReplaceOp@[@0,0:0='a',<1>,1:0]..[@2,2:2='c',<3>,1:2]:\"x\">" |
| ); |
| }); |
| |
| it("throws an error when inserting into a deleted segment", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.delete(0, 2); |
| rewriter.insertBefore(1, "0"); |
| |
| // Assert |
| expect(() => rewriter.getText()).toThrowError( |
| "insert op <InsertBeforeOp@[@1,1:1='b',<2>,1:1]:\"0\"> within boundaries of previous <DeleteOp@[@0,0:0='a',<1>,1:0]..[@2,2:2='c',<3>,1:2]>" |
| ); |
| }); |
| |
| it("inserts '0' before the first token and then replaces it with an 'x'", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(0, "0"); |
| rewriter.replaceSingle(0, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("0xbc"); |
| }); |
| |
| it("inserts texts in reverse order when multiple inserts occur at the same index", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(1, "x"); |
| rewriter.insertBefore(1, "y"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("ayxbc"); |
| }); |
| |
| it("inserts 'y' and 'x' before the first index and then replaces it with 'z'", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(0, "x"); |
| rewriter.insertBefore(0, "y"); |
| rewriter.replaceSingle(0, "z"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("yxzbc"); |
| }); |
| |
| it("replaces the last index with an 'x' and then inserts 'y' before it", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replaceSingle(2, "x"); |
| rewriter.insertBefore(2, "y"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abyx"); |
| }); |
| |
| it("replaces thte last index with an 'x' and then inserts 'y' after it", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replaceSingle(2, "x"); |
| rewriter.insertAfter(2, "y"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abxy"); |
| }); |
| |
| it("replaces a range with an 'x' and then inserts 'y' before the left edge of the range", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcccba"); |
| |
| // Act |
| rewriter.replace(2, 4, "x"); |
| rewriter.insertBefore(2, "y"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abyxba"); |
| }); |
| |
| it("throws an error if an attempt is made to insert a token before the right edge of a replaced range", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcccba"); |
| |
| // Act |
| rewriter.replace(2, 4, "x"); |
| rewriter.insertBefore(4, "y"); |
| |
| // Assert |
| expect(() => rewriter.getText()).toThrowError( |
| "insert op <InsertBeforeOp@[@4,4:4='c',<3>,1:4]:\"y\"> within boundaries of previous <ReplaceOp@[@2,2:2='c',<3>,1:2]..[@4,4:4='c',<3>,1:4]:\"x\">" |
| ); |
| }); |
| |
| it("replaces a range with an 'x' then inserts 'y' after the right edge of the range", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcccba"); |
| |
| // Act |
| rewriter.replace(2, 4, "x"); |
| rewriter.insertAfter(4, "y"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abxyba"); |
| }); |
| |
| it("replaces a token range", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcba"); |
| const bToken = rewriter.tokens.get(1); |
| const dToken = rewriter.tokens.get(3); |
| |
| // Act |
| rewriter.replace(bToken, dToken, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("axa"); |
| }); |
| |
| it("throws an error when replace is given an invalid range", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| const badRanges = [ |
| [1, 0], // from > to |
| [-1, 1], // from is negative |
| [1, -1], // to is negative |
| [-2, -1], // both are negative |
| [1, 4] // to is out of bounds |
| ]; |
| |
| // Act/Assert |
| for (const [from, to] of badRanges) { |
| expect(() => rewriter.replace(from, to, "x")).toThrow(); |
| } |
| }); |
| |
| it("replaces all tokens with an 'x'", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcccba"); |
| |
| // Act |
| rewriter.replace(0, 6, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("x"); |
| }); |
| |
| it("replaces the middle 'ccc' with 'xyz'", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcccba"); |
| |
| // Act |
| rewriter.replace(2, 4, "xyz"); |
| |
| // Assert |
| expect(rewriter.getText(new antlr4.Interval(0, 6))).toEqual("abxyzba"); |
| }); |
| |
| it("throws an error if second replace operation overlaps the first one on the right", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcccba"); |
| |
| // Act |
| rewriter.replace(2, 4, "xyz"); |
| rewriter.replace(3, 5, "foo"); |
| |
| // Assert |
| expect(() => rewriter.getText()).toThrowError( |
| "replace op boundaries of <ReplaceOp@[@3,3:3='c',<3>,1:3]..[@5,5:5='b',<2>,1:5]:\"foo\"> overlap with previous <ReplaceOp@[@2,2:2='c',<3>,1:2]..[@4,4:4='c',<3>,1:4]:\"xyz\">" |
| ); |
| }); |
| |
| it("throws an error if second replace operation overlaps the first one on the left", () => { |
| // Arrange |
| const chars = antlr4.CharStreams.fromString("abcccba"); |
| const lexer = new abc(chars); |
| const tokens = new antlr4.CommonTokenStream(lexer); |
| tokens.fill(); |
| const rewriter = new antlr4.TokenStreamRewriter(tokens); |
| |
| // Act |
| rewriter.replace(2, 4, "xyz"); |
| rewriter.replace(1, 3, "foo"); |
| |
| // Assert |
| expect(() => rewriter.getText()).toThrowError( |
| "replace op boundaries of <ReplaceOp@[@1,1:1='b',<2>,1:1]..[@3,3:3='c',<3>,1:3]:\"foo\"> overlap with previous <ReplaceOp@[@2,2:2='c',<3>,1:2]..[@4,4:4='c',<3>,1:4]:\"xyz\">" |
| ); |
| }); |
| |
| it("ignores first replace operation when the second one overlaps it on both sides (superset)", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcba"); |
| |
| // Act |
| rewriter.replace(2, 2, "xyz"); |
| rewriter.replace(0, 3, "foo"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("fooa"); |
| }); |
| |
| it("inserts 'x' and 'y' before the first token", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(0, "x"); |
| rewriter.insertBefore(0, "y"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("yxabc"); |
| }); |
| |
| it("performs 3 inserts at 2 locations", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(1, "x"); |
| rewriter.insertBefore(0, "y"); |
| rewriter.insertBefore(1, "z"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("yazxbc"); |
| }); |
| |
| it("replaces 'abc' with 'foo' and then inserts 'z' before it", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.replace(0, 2, "foo"); |
| rewriter.insertBefore(0, "z"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("zfoo"); |
| }); |
| |
| it("deletes 'abc' and then inserts 'z' before it", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.delete(0, 2); |
| rewriter.insertBefore(0, "z"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("z"); |
| }); |
| |
| |
| it("makes 3 inserts at 3 locations", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(1, "x"); |
| rewriter.insertBefore(2, "y"); |
| rewriter.insertBefore(0, "z"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("zaxbyc"); |
| }); |
| |
| it("throws an error if second replace operation affects a subset of a previous one", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcc"); |
| |
| // Act |
| rewriter.replace(0, 3, "bar"); |
| rewriter.replace(1, 2, "foo"); |
| |
| // Assert |
| expect(() => rewriter.getText()).toThrowError( |
| "replace op boundaries of <ReplaceOp@[@1,1:1='b',<2>,1:1]..[@2,2:2='c',<3>,1:2]:\"foo\"> overlap with previous <ReplaceOp@[@0,0:0='a',<1>,1:0]..[@3,3:3='c',<3>,1:3]:\"bar\">" |
| ); |
| }); |
| |
| it("ignores the first replace operation when the secone one extends it to the left", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcc"); |
| |
| // Act |
| rewriter.replace(1, 2, "foo"); |
| rewriter.replace(0, 2, "bar"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("barc"); |
| }); |
| |
| it("ignores the first replace operation when the secone one extends it to the right", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcc"); |
| |
| // Act |
| rewriter.replace(1, 2, "foo"); |
| rewriter.replace(1, 3, "bar"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("abar"); |
| }); |
| |
| it("only applies one replace operation when identical ones are given", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcc"); |
| |
| // Act |
| rewriter.replace(1, 2, "foo"); |
| rewriter.replace(1, 2, "foo"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("afooc"); |
| }); |
| |
| it("drops the insert operation when it is covered by a subsequent replace operation", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(2, "foo"); |
| rewriter.replace(1, 2, "foo"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("afoo"); |
| }); |
| |
| it("performs the insert operation when disjoint from the replace operation (1 of 2)", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcc"); |
| |
| // Act |
| rewriter.insertBefore(1, "x"); |
| rewriter.replace(2, 3, "foo"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("axbfoo"); |
| }); |
| |
| it("performs the insert operation when disjoint from the replace operation (2 of 2)", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcc"); |
| |
| // Act |
| rewriter.replace(2, 3, "foo"); |
| rewriter.insertBefore(1, "x"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("axbfoo"); |
| }); |
| |
| it("inserts 'y' before the last token, then deletes it", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(2, "y"); |
| rewriter.delete(2); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("aby"); |
| }); |
| |
| it("deletes the 'a' token", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| const aToken = rewriter.tokens.get(0); |
| |
| // Act |
| rewriter.delete(aToken); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("bc"); |
| }); |
| |
| // Test for https://github.com/antlr/antlr4/issues/550 |
| it("distinguishes between insertAfter and insertBefore to preserve order (1 of 2)", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "aa"); |
| |
| // Act |
| rewriter.insertBefore(0, "<b>"); |
| rewriter.insertAfter(0, "</b>"); |
| rewriter.insertBefore(1, "<b>"); |
| rewriter.insertAfter(1, "</b>"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("<b>a</b><b>a</b>"); |
| }); |
| |
| it("distinguishes between insertAfter and insertBefore to preserve order (2 of 2)", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "aa"); |
| |
| // Act |
| rewriter.insertBefore(0, "<p>"); |
| rewriter.insertBefore(0, "<b>"); |
| rewriter.insertAfter(0, "</p>"); |
| rewriter.insertAfter(0, "</b>"); |
| rewriter.insertBefore(1, "<b>"); |
| rewriter.insertAfter(1, "</b>"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("<b><p>a</p></b><b>a</b>"); |
| }); |
| |
| it("preserves the order of contiguous inserts", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "ab"); |
| |
| // Act |
| rewriter.insertBefore(0, "<p>"); |
| rewriter.insertBefore(0, "<b>"); |
| rewriter.insertBefore(0, "<div>"); |
| rewriter.insertAfter(0, "</p>"); |
| rewriter.insertAfter(0, "</b>"); |
| rewriter.insertAfter(0, "</div>"); |
| rewriter.insertBefore(1, "!"); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("<div><b><p>a</p></b></div>!b"); |
| }); |
| |
| it("accepts different types as text", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(0, false); |
| rewriter.insertBefore(0, 0); |
| rewriter.insertBefore(0, {}); |
| rewriter.insertBefore(0, []); |
| rewriter.insertBefore(0, ""); |
| rewriter.insertBefore(0, null); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("[object Object]0falseabc"); |
| }); |
| |
| it("returns the original input if no rewrites have occurred", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| const result = rewriter.getText(); |
| |
| // Assert |
| expect(result).toEqual("abc"); |
| }); |
| |
| it("segments operations by program name", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| rewriter.insertBefore(0, "b", "P1"); |
| rewriter.insertAfter(0, "c", "P2"); |
| rewriter.replaceSingle(2, "b", "P2"); |
| |
| // Assert |
| expect(rewriter.getText("P1")).toEqual("babc"); |
| expect(rewriter.getText("P2")).toEqual("acbb"); |
| }); |
| |
| it("doesn't make a fuss if getText is supplied with an interval that exceeds the token range", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abc"); |
| |
| // Act |
| const unmodified = rewriter.getText(new antlr4.Interval(-1, 3)); |
| rewriter.insertAfter(2, "a"); |
| const modified = rewriter.getText(new antlr4.Interval(0, 200)); |
| |
| // Assert |
| expect(unmodified).toEqual("abc"); |
| expect(modified).toEqual("abca"); |
| }); |
| |
| it("ignores inserts that occur within a removed range", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcba"); |
| |
| // Act |
| rewriter.insertAfter(2, "c"); |
| rewriter.delete(2, 3); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("aba"); |
| }); |
| |
| it("handles overlapping delete ranges", () => { |
| // Arrange |
| const rewriter = getRewriter(abc, "abcba"); |
| |
| // Act |
| rewriter.delete(1, 3); |
| rewriter.delete(2, 4); |
| |
| // Assert |
| expect(rewriter.getText()).toEqual("a"); |
| }); |
| }); |