/** * @fileoverview Tests for TokenStore class. * @author Brandon Mills */ "use strict"; //------------------------------------------------------------------------------ // Requirements //------------------------------------------------------------------------------ const assert = require("chai").assert, espree = require("espree"), TokenStore = require("../../../lib/source-code/token-store"); //------------------------------------------------------------------------------ // Constants //------------------------------------------------------------------------------ const SOURCE_CODE = "/*A*/var answer/*B*/=/*C*/a/*D*/* b/*E*///F\n call();\n/*Z*/", AST = espree.parse(SOURCE_CODE, { loc: true, range: true, tokens: true, comment: true }), TOKENS = AST.tokens, COMMENTS = AST.comments, Program = AST, VariableDeclaration = Program.body[0], VariableDeclarator = VariableDeclaration.declarations[0], BinaryExpression = VariableDeclarator.init, CallExpression = Program.body[1].expression; //------------------------------------------------------------------------------ // Helpers //------------------------------------------------------------------------------ /** * Checks the values of tokens against an array of expected values. * @param {Token[]} tokens Tokens returned from the API. * @param {string[]} expected Expected token values * @returns {void} */ function check(tokens, expected) { const length = tokens.length; assert.strictEqual(length, expected.length); for (let i = 0; i < length; i++) { assert.strictEqual(tokens[i].value, expected[i]); } } //------------------------------------------------------------------------------ // Tests //------------------------------------------------------------------------------ describe("TokenStore", () => { const store = new TokenStore(TOKENS, COMMENTS); describe("when calling getTokens", () => { it("should retrieve all tokens for root node", () => { check( store.getTokens(Program), ["var", "answer", "=", "a", "*", "b", "call", "(", ")", ";"] ); }); it("should retrieve all tokens for binary expression", () => { check( store.getTokens(BinaryExpression), ["a", "*", "b"] ); }); it("should retrieve all tokens plus one before for binary expression", () => { check( store.getTokens(BinaryExpression, 1), ["=", "a", "*", "b"] ); }); it("should retrieve all tokens plus one after for binary expression", () => { check( store.getTokens(BinaryExpression, 0, 1), ["a", "*", "b", "call"] ); }); it("should retrieve all tokens plus two before and one after for binary expression", () => { check( store.getTokens(BinaryExpression, 2, 1), ["answer", "=", "a", "*", "b", "call"] ); }); it("should retrieve all matched tokens for root node with filter", () => { check( store.getTokens(Program, t => t.type === "Identifier"), ["answer", "a", "b", "call"] ); check( store.getTokens(Program, { filter: t => t.type === "Identifier" }), ["answer", "a", "b", "call"] ); }); it("should retrieve all tokens and comments in the node for root node with includeComments option", () => { check( store.getTokens(Program, { includeComments: true }), ["var", "answer", "B", "=", "C", "a", "D", "*", "b", "E", "F", "call", "(", ")", ";"] ); }); it("should retrieve matched tokens and comments in the node for root node with includeComments and filter options", () => { check( store.getTokens(Program, { includeComments: true, filter: t => t.type.startsWith("Block") }), ["B", "C", "D", "E"] ); }); it("should retrieve all tokens and comments in the node for binary expression with includeComments option", () => { check( store.getTokens(BinaryExpression, { includeComments: true }), ["a", "D", "*", "b"] ); }); }); describe("when calling getTokensBefore", () => { it("should retrieve zero tokens before a node", () => { check( store.getTokensBefore(BinaryExpression, 0), [] ); }); it("should retrieve one token before a node", () => { check( store.getTokensBefore(BinaryExpression, 1), ["="] ); }); it("should retrieve more than one token before a node", () => { check( store.getTokensBefore(BinaryExpression, 2), ["answer", "="] ); }); it("should retrieve all tokens before a node", () => { check( store.getTokensBefore(BinaryExpression, 9e9), ["var", "answer", "="] ); }); it("should retrieve more than one token before a node with count option", () => { check( store.getTokensBefore(BinaryExpression, { count: 2 }), ["answer", "="] ); }); it("should retrieve matched tokens before a node with count and filter options", () => { check( store.getTokensBefore(BinaryExpression, { count: 1, filter: t => t.value !== "=" }), ["answer"] ); }); it("should retrieve all matched tokens before a node with filter option", () => { check( store.getTokensBefore(BinaryExpression, { filter: t => t.value !== "answer" }), ["var", "="] ); }); it("should retrieve no tokens before the root node", () => { check( store.getTokensBefore(Program, { count: 1 }), [] ); }); it("should retrieve tokens and comments before a node with count and includeComments option", () => { check( store.getTokensBefore(BinaryExpression, { count: 3, includeComments: true }), ["B", "=", "C"] ); }); it("should retrieve all tokens and comments before a node with includeComments option only", () => { check( store.getTokensBefore(BinaryExpression, { includeComments: true }), ["A", "var", "answer", "B", "=", "C"] ); }); it("should retrieve all tokens and comments before a node with includeComments and filter options", () => { check( store.getTokensBefore(BinaryExpression, { includeComments: true, filter: t => t.type.startsWith("Block") }), ["A", "B", "C"] ); }); }); describe("when calling getTokenBefore", () => { it("should retrieve one token before a node", () => { assert.strictEqual( store.getTokenBefore(BinaryExpression).value, "=" ); }); it("should skip a given number of tokens", () => { assert.strictEqual( store.getTokenBefore(BinaryExpression, 1).value, "answer" ); assert.strictEqual( store.getTokenBefore(BinaryExpression, 2).value, "var" ); }); it("should skip a given number of tokens with skip option", () => { assert.strictEqual( store.getTokenBefore(BinaryExpression, { skip: 1 }).value, "answer" ); assert.strictEqual( store.getTokenBefore(BinaryExpression, { skip: 2 }).value, "var" ); }); it("should retrieve matched token with filter option", () => { assert.strictEqual( store.getTokenBefore(BinaryExpression, t => t.value !== "=").value, "answer" ); }); it("should retrieve matched token with skip and filter options", () => { assert.strictEqual( store.getTokenBefore(BinaryExpression, { skip: 1, filter: t => t.value !== "=" }).value, "var" ); }); it("should retrieve one token or comment before a node with includeComments option", () => { assert.strictEqual( store.getTokenBefore(BinaryExpression, { includeComments: true }).value, "C" ); }); it("should retrieve one token or comment before a node with includeComments and skip options", () => { assert.strictEqual( store.getTokenBefore(BinaryExpression, { includeComments: true, skip: 1 }).value, "=" ); }); it("should retrieve one token or comment before a node with includeComments and skip and filter options", () => { assert.strictEqual( store.getTokenBefore(BinaryExpression, { includeComments: true, skip: 1, filter: t => t.type.startsWith("Block") }).value, "B" ); }); it("should retrieve the previous node if the comment at the end of source code is specified.", () => { const code = "a + b /*comment*/"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getTokenBefore(ast.comments[0]); assert.strictEqual(token.value, "b"); }); it("should retrieve the previous comment if the first token is specified.", () => { const code = "/*comment*/ a + b"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getTokenBefore(ast.tokens[0], { includeComments: true }); assert.strictEqual(token.value, "comment"); }); it("should retrieve null if the first comment is specified.", () => { const code = "/*comment*/ a + b"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getTokenBefore(ast.comments[0], { includeComments: true }); assert.strictEqual(token, null); }); }); describe("when calling getTokensAfter", () => { it("should retrieve zero tokens after a node", () => { check( store.getTokensAfter(VariableDeclarator.id, 0), [] ); }); it("should retrieve one token after a node", () => { check( store.getTokensAfter(VariableDeclarator.id, 1), ["="] ); }); it("should retrieve more than one token after a node", () => { check( store.getTokensAfter(VariableDeclarator.id, 2), ["=", "a"] ); }); it("should retrieve all tokens after a node", () => { check( store.getTokensAfter(VariableDeclarator.id, 9e9), ["=", "a", "*", "b", "call", "(", ")", ";"] ); }); it("should retrieve more than one token after a node with count option", () => { check( store.getTokensAfter(VariableDeclarator.id, { count: 2 }), ["=", "a"] ); }); it("should retrieve all matched tokens after a node with filter option", () => { check( store.getTokensAfter(VariableDeclarator.id, { filter: t => t.type === "Identifier" }), ["a", "b", "call"] ); }); it("should retrieve matched tokens after a node with count and filter options", () => { check( store.getTokensAfter(VariableDeclarator.id, { count: 2, filter: t => t.type === "Identifier" }), ["a", "b"] ); }); it("should retrieve all tokens and comments after a node with includeComments option", () => { check( store.getTokensAfter(VariableDeclarator.id, { includeComments: true }), ["B", "=", "C", "a", "D", "*", "b", "E", "F", "call", "(", ")", ";", "Z"] ); }); it("should retrieve several tokens and comments after a node with includeComments and count options", () => { check( store.getTokensAfter(VariableDeclarator.id, { includeComments: true, count: 3 }), ["B", "=", "C"] ); }); it("should retrieve matched tokens and comments after a node with includeComments and count and filter options", () => { check( store.getTokensAfter(VariableDeclarator.id, { includeComments: true, count: 3, filter: t => t.type.startsWith("Block") }), ["B", "C", "D"] ); }); }); describe("when calling getTokenAfter", () => { it("should retrieve one token after a node", () => { assert.strictEqual( store.getTokenAfter(VariableDeclarator.id).value, "=" ); }); it("should skip a given number of tokens", () => { assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, 1).value, "a" ); assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, 2).value, "*" ); }); it("should skip a given number of tokens with skip option", () => { assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, { skip: 1 }).value, "a" ); assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, { skip: 2 }).value, "*" ); }); it("should retrieve matched token with filter option", () => { assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, t => t.type === "Identifier").value, "a" ); assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, { filter: t => t.type === "Identifier" }).value, "a" ); }); it("should retrieve matched token with filter and skip options", () => { assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, { skip: 1, filter: t => t.type === "Identifier" }).value, "b" ); }); it("should retrieve one token or comment after a node with includeComments option", () => { assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, { includeComments: true }).value, "B" ); }); it("should retrieve one token or comment after a node with includeComments and skip options", () => { assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, { includeComments: true, skip: 2 }).value, "C" ); }); it("should retrieve one token or comment after a node with includeComments and skip and filter options", () => { assert.strictEqual( store.getTokenAfter(VariableDeclarator.id, { includeComments: true, skip: 2, filter: t => t.type.startsWith("Block") }).value, "D" ); }); it("should retrieve the next node if the comment at the first of source code is specified.", () => { const code = "/*comment*/ a + b"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getTokenAfter(ast.comments[0]); assert.strictEqual(token.value, "a"); }); it("should retrieve the next comment if the last token is specified.", () => { const code = "a + b /*comment*/"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getTokenAfter(ast.tokens[2], { includeComments: true }); assert.strictEqual(token.value, "comment"); }); it("should retrieve null if the last comment is specified.", () => { const code = "a + b /*comment*/"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getTokenAfter(ast.comments[0], { includeComments: true }); assert.strictEqual(token, null); }); }); describe("when calling getFirstTokens", () => { it("should retrieve zero tokens from a node's token stream", () => { check( store.getFirstTokens(BinaryExpression, 0), [] ); }); it("should retrieve one token from a node's token stream", () => { check( store.getFirstTokens(BinaryExpression, 1), ["a"] ); }); it("should retrieve more than one token from a node's token stream", () => { check( store.getFirstTokens(BinaryExpression, 2), ["a", "*"] ); }); it("should retrieve all tokens from a node's token stream", () => { check( store.getFirstTokens(BinaryExpression, 9e9), ["a", "*", "b"] ); }); it("should retrieve more than one token from a node's token stream with count option", () => { check( store.getFirstTokens(BinaryExpression, { count: 2 }), ["a", "*"] ); }); it("should retrieve matched tokens from a node's token stream with filter option", () => { check( store.getFirstTokens(BinaryExpression, t => t.type === "Identifier"), ["a", "b"] ); check( store.getFirstTokens(BinaryExpression, { filter: t => t.type === "Identifier" }), ["a", "b"] ); }); it("should retrieve matched tokens from a node's token stream with filter and count options", () => { check( store.getFirstTokens(BinaryExpression, { count: 1, filter: t => t.type === "Identifier" }), ["a"] ); }); it("should retrieve all tokens and comments from a node's token stream with includeComments option", () => { check( store.getFirstTokens(BinaryExpression, { includeComments: true }), ["a", "D", "*", "b"] ); }); it("should retrieve several tokens and comments from a node's token stream with includeComments and count options", () => { check( store.getFirstTokens(BinaryExpression, { includeComments: true, count: 3 }), ["a", "D", "*"] ); }); it("should retrieve several tokens and comments from a node's token stream with includeComments and count and filter options", () => { check( store.getFirstTokens(BinaryExpression, { includeComments: true, count: 3, filter: t => t.value !== "a" }), ["D", "*", "b"] ); }); }); describe("when calling getFirstToken", () => { it("should retrieve the first token of a node's token stream", () => { assert.strictEqual( store.getFirstToken(BinaryExpression).value, "a" ); }); it("should skip a given number of tokens", () => { assert.strictEqual( store.getFirstToken(BinaryExpression, 1).value, "*" ); assert.strictEqual( store.getFirstToken(BinaryExpression, 2).value, "b" ); }); it("should skip a given number of tokens with skip option", () => { assert.strictEqual( store.getFirstToken(BinaryExpression, { skip: 1 }).value, "*" ); assert.strictEqual( store.getFirstToken(BinaryExpression, { skip: 2 }).value, "b" ); }); it("should retrieve matched token with filter option", () => { assert.strictEqual( store.getFirstToken(BinaryExpression, t => t.type === "Identifier").value, "a" ); assert.strictEqual( store.getFirstToken(BinaryExpression, { filter: t => t.type === "Identifier" }).value, "a" ); }); it("should retrieve matched token with filter and skip options", () => { assert.strictEqual( store.getFirstToken(BinaryExpression, { skip: 1, filter: t => t.type === "Identifier" }).value, "b" ); }); it("should retrieve the first token or comment of a node's token stream with includeComments option", () => { assert.strictEqual( store.getFirstToken(BinaryExpression, { includeComments: true }).value, "a" ); }); it("should retrieve the first matched token or comment of a node's token stream with includeComments and skip options", () => { assert.strictEqual( store.getFirstToken(BinaryExpression, { includeComments: true, skip: 1 }).value, "D" ); }); it("should retrieve the first matched token or comment of a node's token stream with includeComments and skip and filter options", () => { assert.strictEqual( store.getFirstToken(BinaryExpression, { includeComments: true, skip: 1, filter: t => t.value !== "a" }).value, "*" ); }); it("should retrieve the first comment if the comment is at the last of nodes", () => { const code = "a + b\n/*comment*/ c + d"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); /* * A node must not start with a token: it can start with a comment or be empty. * This test case is needed for completeness. */ const token = tokenStore.getFirstToken( { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] }, { includeComments: true } ); assert.strictEqual(token.value, "comment"); }); it("should retrieve the first token (without includeComments option) if the comment is at the last of nodes", () => { const code = "a + b\n/*comment*/ c + d"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); /* * A node must not start with a token: it can start with a comment or be empty. * This test case is needed for completeness. */ const token = tokenStore.getFirstToken( { range: [ast.comments[0].range[0], ast.tokens[5].range[1]] } ); assert.strictEqual(token.value, "c"); }); it("should retrieve the first token if the root node contains a trailing comment", () => { const parser = require("../../fixtures/parsers/all-comments-parser"); const code = "foo // comment"; const ast = parser.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getFirstToken(ast); assert.strictEqual(token, ast.tokens[0]); }); it("should return null if the source contains only comments", () => { const code = "// comment"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getFirstToken(ast, { filter() { assert.fail("Unexpected call to filter callback"); } }); assert.strictEqual(token, null); }); it("should return null if the source is empty", () => { const code = ""; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getFirstToken(ast); assert.strictEqual(token, null); }); }); describe("when calling getLastTokens", () => { it("should retrieve zero tokens from the end of a node's token stream", () => { check( store.getLastTokens(BinaryExpression, 0), [] ); }); it("should retrieve one token from the end of a node's token stream", () => { check( store.getLastTokens(BinaryExpression, 1), ["b"] ); }); it("should retrieve more than one token from the end of a node's token stream", () => { check( store.getLastTokens(BinaryExpression, 2), ["*", "b"] ); }); it("should retrieve all tokens from the end of a node's token stream", () => { check( store.getLastTokens(BinaryExpression, 9e9), ["a", "*", "b"] ); }); it("should retrieve more than one token from the end of a node's token stream with count option", () => { check( store.getLastTokens(BinaryExpression, { count: 2 }), ["*", "b"] ); }); it("should retrieve matched tokens from the end of a node's token stream with filter option", () => { check( store.getLastTokens(BinaryExpression, t => t.type === "Identifier"), ["a", "b"] ); check( store.getLastTokens(BinaryExpression, { filter: t => t.type === "Identifier" }), ["a", "b"] ); }); it("should retrieve matched tokens from the end of a node's token stream with filter and count options", () => { check( store.getLastTokens(BinaryExpression, { count: 1, filter: t => t.type === "Identifier" }), ["b"] ); }); it("should retrieve all tokens from the end of a node's token stream with includeComments option", () => { check( store.getLastTokens(BinaryExpression, { includeComments: true }), ["a", "D", "*", "b"] ); }); it("should retrieve matched tokens from the end of a node's token stream with includeComments and count options", () => { check( store.getLastTokens(BinaryExpression, { includeComments: true, count: 3 }), ["D", "*", "b"] ); }); it("should retrieve matched tokens from the end of a node's token stream with includeComments and count and filter options", () => { check( store.getLastTokens(BinaryExpression, { includeComments: true, count: 3, filter: t => t.type !== "Punctuator" }), ["a", "D", "b"] ); }); }); describe("when calling getLastToken", () => { it("should retrieve the last token of a node's token stream", () => { assert.strictEqual( store.getLastToken(BinaryExpression).value, "b" ); assert.strictEqual( store.getLastToken(VariableDeclaration).value, "b" ); }); it("should skip a given number of tokens", () => { assert.strictEqual( store.getLastToken(BinaryExpression, 1).value, "*" ); assert.strictEqual( store.getLastToken(BinaryExpression, 2).value, "a" ); }); it("should skip a given number of tokens with skip option", () => { assert.strictEqual( store.getLastToken(BinaryExpression, { skip: 1 }).value, "*" ); assert.strictEqual( store.getLastToken(BinaryExpression, { skip: 2 }).value, "a" ); }); it("should retrieve the last matched token of a node's token stream with filter option", () => { assert.strictEqual( store.getLastToken(BinaryExpression, t => t.value !== "b").value, "*" ); assert.strictEqual( store.getLastToken(BinaryExpression, { filter: t => t.value !== "b" }).value, "*" ); }); it("should retrieve the last matched token of a node's token stream with filter and skip options", () => { assert.strictEqual( store.getLastToken(BinaryExpression, { skip: 1, filter: t => t.type === "Identifier" }).value, "a" ); }); it("should retrieve the last token of a node's token stream with includeComments option", () => { assert.strictEqual( store.getLastToken(BinaryExpression, { includeComments: true }).value, "b" ); }); it("should retrieve the last token of a node's token stream with includeComments and skip options", () => { assert.strictEqual( store.getLastToken(BinaryExpression, { includeComments: true, skip: 2 }).value, "D" ); }); it("should retrieve the last token of a node's token stream with includeComments and skip and filter options", () => { assert.strictEqual( store.getLastToken(BinaryExpression, { includeComments: true, skip: 1, filter: t => t.type !== "Identifier" }).value, "D" ); }); it("should retrieve the last comment if the comment is at the last of nodes", () => { const code = "a + b /*comment*/\nc + d"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); /* * A node must not end with a token: it can end with a comment or be empty. * This test case is needed for completeness. */ const token = tokenStore.getLastToken( { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] }, { includeComments: true } ); assert.strictEqual(token.value, "comment"); }); it("should retrieve the last token (without includeComments option) if the comment is at the last of nodes", () => { const code = "a + b /*comment*/\nc + d"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); /* * A node must not end with a token: it can end with a comment or be empty. * This test case is needed for completeness. */ const token = tokenStore.getLastToken( { range: [ast.tokens[0].range[0], ast.comments[0].range[1]] } ); assert.strictEqual(token.value, "b"); }); it("should retrieve the last token if the root node contains a trailing comment", () => { const parser = require("../../fixtures/parsers/all-comments-parser"); const code = "foo // comment"; const ast = parser.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getLastToken(ast); assert.strictEqual(token, ast.tokens[0]); }); it("should return null if the source contains only comments", () => { const code = "// comment"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getLastToken(ast, { filter() { assert.fail("Unexpected call to filter callback"); } }); assert.strictEqual(token, null); }); it("should return null if the source is empty", () => { const code = ""; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const token = tokenStore.getLastToken(ast); assert.strictEqual(token, null); }); }); describe("when calling getFirstTokensBetween", () => { it("should retrieve zero tokens between adjacent nodes", () => { check( store.getFirstTokensBetween(BinaryExpression, CallExpression), [] ); }); it("should retrieve multiple tokens between non-adjacent nodes with count option", () => { check( store.getFirstTokensBetween(VariableDeclarator.id, BinaryExpression.right, 2), ["=", "a"] ); check( store.getFirstTokensBetween(VariableDeclarator.id, BinaryExpression.right, { count: 2 }), ["=", "a"] ); }); it("should retrieve matched tokens between non-adjacent nodes with filter option", () => { check( store.getFirstTokensBetween(VariableDeclarator.id, BinaryExpression.right, { filter: t => t.type !== "Punctuator" }), ["a"] ); }); it("should retrieve all tokens between non-adjacent nodes with empty object option", () => { check( store.getFirstTokensBetween(VariableDeclarator.id, BinaryExpression.right, {}), ["=", "a", "*"] ); }); it("should retrieve multiple tokens between non-adjacent nodes with includeComments option", () => { check( store.getFirstTokensBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true }), ["B", "=", "C", "a", "D", "*"] ); }); it("should retrieve multiple tokens between non-adjacent nodes with includeComments and count options", () => { check( store.getFirstTokensBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true, count: 3 }), ["B", "=", "C"] ); }); it("should retrieve multiple tokens and comments between non-adjacent nodes with includeComments and filter options", () => { check( store.getFirstTokensBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true, filter: t => t.type !== "Punctuator" }), ["B", "C", "a", "D"] ); }); }); describe("when calling getFirstTokenBetween", () => { it("should return null between adjacent nodes", () => { assert.strictEqual( store.getFirstTokenBetween(BinaryExpression, CallExpression), null ); }); it("should retrieve one token between non-adjacent nodes with count option", () => { assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right).value, "=" ); }); it("should retrieve one token between non-adjacent nodes with skip option", () => { assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right, 1).value, "a" ); assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right, { skip: 2 }).value, "*" ); }); it("should return null if it's skipped beyond the right token", () => { assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right, { skip: 3 }), null ); assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right, { skip: 4 }), null ); }); it("should retrieve the first matched token between non-adjacent nodes with filter option", () => { assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right, { filter: t => t.type !== "Identifier" }).value, "=" ); }); it("should retrieve first token or comment between non-adjacent nodes with includeComments option", () => { assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true }).value, "B" ); }); it("should retrieve first token or comment between non-adjacent nodes with includeComments and skip options", () => { assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true, skip: 1 }).value, "=" ); }); it("should retrieve first token or comment between non-adjacent nodes with includeComments and skip and filter options", () => { assert.strictEqual( store.getFirstTokenBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true, skip: 1, filter: t => t.type !== "Punctuator" }).value, "C" ); }); }); describe("when calling getLastTokensBetween", () => { it("should retrieve zero tokens between adjacent nodes", () => { check( store.getLastTokensBetween(BinaryExpression, CallExpression), [] ); }); it("should retrieve multiple tokens between non-adjacent nodes with count option", () => { check( store.getLastTokensBetween(VariableDeclarator.id, BinaryExpression.right, 2), ["a", "*"] ); check( store.getLastTokensBetween(VariableDeclarator.id, BinaryExpression.right, { count: 2 }), ["a", "*"] ); }); it("should retrieve matched tokens between non-adjacent nodes with filter option", () => { check( store.getLastTokensBetween(VariableDeclarator.id, BinaryExpression.right, { filter: t => t.type !== "Punctuator" }), ["a"] ); }); it("should retrieve all tokens between non-adjacent nodes with empty object option", () => { check( store.getLastTokensBetween(VariableDeclarator.id, BinaryExpression.right, {}), ["=", "a", "*"] ); }); it("should retrieve all tokens and comments between non-adjacent nodes with includeComments option", () => { check( store.getLastTokensBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true }), ["B", "=", "C", "a", "D", "*"] ); }); it("should retrieve multiple tokens between non-adjacent nodes with includeComments and count options", () => { check( store.getLastTokensBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true, count: 3 }), ["a", "D", "*"] ); }); it("should retrieve multiple tokens and comments between non-adjacent nodes with includeComments and filter options", () => { check( store.getLastTokensBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true, filter: t => t.type !== "Punctuator" }), ["B", "C", "a", "D"] ); }); }); describe("when calling getLastTokenBetween", () => { it("should return null between adjacent nodes", () => { assert.strictEqual( store.getLastTokenBetween(BinaryExpression, CallExpression), null ); }); it("should retrieve one token between non-adjacent nodes with count option", () => { assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right).value, "*" ); }); it("should retrieve one token between non-adjacent nodes with skip option", () => { assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right, 1).value, "a" ); assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right, { skip: 2 }).value, "=" ); }); it("should return null if it's skipped beyond the right token", () => { assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right, { skip: 3 }), null ); assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right, { skip: 4 }), null ); }); it("should retrieve the first matched token between non-adjacent nodes with filter option", () => { assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right, { filter: t => t.type !== "Identifier" }).value, "*" ); }); it("should retrieve first token or comment between non-adjacent nodes with includeComments option", () => { assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true }).value, "*" ); }); it("should retrieve first token or comment between non-adjacent nodes with includeComments and skip options", () => { assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true, skip: 1 }).value, "D" ); }); it("should retrieve first token or comment between non-adjacent nodes with includeComments and skip and filter options", () => { assert.strictEqual( store.getLastTokenBetween(VariableDeclarator.id, BinaryExpression.right, { includeComments: true, skip: 1, filter: t => t.type !== "Punctuator" }).value, "a" ); }); }); describe("when calling getTokensBetween", () => { it("should retrieve zero tokens between adjacent nodes", () => { check( store.getTokensBetween(BinaryExpression, CallExpression), [] ); }); it("should retrieve one token between nodes", () => { check( store.getTokensBetween(BinaryExpression.left, BinaryExpression.right), ["*"] ); }); it("should retrieve multiple tokens between non-adjacent nodes", () => { check( store.getTokensBetween(VariableDeclarator.id, BinaryExpression.right), ["=", "a", "*"] ); }); it("should retrieve surrounding tokens when asked for padding", () => { check( store.getTokensBetween(VariableDeclarator.id, BinaryExpression.left, 2), ["var", "answer", "=", "a", "*"] ); }); }); describe("when calling getTokenByRangeStart", () => { it("should return identifier token", () => { const result = store.getTokenByRangeStart(9); assert.strictEqual(result.type, "Identifier"); assert.strictEqual(result.value, "answer"); }); it("should return null when token doesn't exist", () => { const result = store.getTokenByRangeStart(10); assert.isNull(result); }); it("should return a comment token when includeComments is true", () => { const result = store.getTokenByRangeStart(15, { includeComments: true }); assert.strictEqual(result.type, "Block"); assert.strictEqual(result.value, "B"); }); it("should not return a comment token at the supplied index when includeComments is false", () => { const result = store.getTokenByRangeStart(15, { includeComments: false }); assert.isNull(result); }); it("should not return comment tokens by default", () => { const result = store.getTokenByRangeStart(15); assert.isNull(result); }); }); describe("when calling getTokenOrCommentBefore", () => { it("should retrieve one token or comment before a node", () => { assert.strictEqual( store.getTokenOrCommentBefore(BinaryExpression).value, "C" ); }); it("should skip a given number of tokens", () => { assert.strictEqual( store.getTokenOrCommentBefore(BinaryExpression, 1).value, "=" ); assert.strictEqual( store.getTokenOrCommentBefore(BinaryExpression, 2).value, "B" ); }); }); describe("when calling getTokenOrCommentAfter", () => { it("should retrieve one token or comment after a node", () => { assert.strictEqual( store.getTokenOrCommentAfter(VariableDeclarator.id).value, "B" ); }); it("should skip a given number of tokens", () => { assert.strictEqual( store.getTokenOrCommentAfter(VariableDeclarator.id, 1).value, "=" ); assert.strictEqual( store.getTokenOrCommentAfter(VariableDeclarator.id, 2).value, "C" ); }); }); describe("when calling getFirstToken & getTokenAfter", () => { it("should retrieve all tokens and comments in the node", () => { const code = "(function(a, /*b,*/ c){})"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const tokens = []; let token = tokenStore.getFirstToken(ast); while (token) { tokens.push(token); token = tokenStore.getTokenAfter(token, { includeComments: true }); } check( tokens, ["(", "function", "(", "a", ",", "b,", "c", ")", "{", "}", ")"] ); }); it("should retrieve all tokens and comments in the node (no spaces)", () => { const code = "(function(a,/*b,*/c){})"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const tokens = []; let token = tokenStore.getFirstToken(ast); while (token) { tokens.push(token); token = tokenStore.getTokenAfter(token, { includeComments: true }); } check( tokens, ["(", "function", "(", "a", ",", "b,", "c", ")", "{", "}", ")"] ); }); }); describe("when calling getLastToken & getTokenBefore", () => { it("should retrieve all tokens and comments in the node", () => { const code = "(function(a, /*b,*/ c){})"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const tokens = []; let token = tokenStore.getLastToken(ast); while (token) { tokens.push(token); token = tokenStore.getTokenBefore(token, { includeComments: true }); } check( tokens.reverse(), ["(", "function", "(", "a", ",", "b,", "c", ")", "{", "}", ")"] ); }); it("should retrieve all tokens and comments in the node (no spaces)", () => { const code = "(function(a,/*b,*/c){})"; const ast = espree.parse(code, { loc: true, range: true, tokens: true, comment: true }); const tokenStore = new TokenStore(ast.tokens, ast.comments); const tokens = []; let token = tokenStore.getLastToken(ast); while (token) { tokens.push(token); token = tokenStore.getTokenBefore(token, { includeComments: true }); } check( tokens.reverse(), ["(", "function", "(", "a", ",", "b,", "c", ")", "{", "}", ")"] ); }); }); describe("when calling commentsExistBetween", () => { it("should retrieve false if comments don't exist", () => { assert.isFalse(store.commentsExistBetween(AST.tokens[0], AST.tokens[1])); }); it("should retrieve true if comments exist", () => { assert.isTrue(store.commentsExistBetween(AST.tokens[1], AST.tokens[2])); }); }); describe("getCommentsBefore", () => { it("should retrieve comments before a node", () => { assert.strictEqual( store.getCommentsBefore(VariableDeclaration)[0].value, "A" ); }); it("should retrieve comments before a token", () => { assert.strictEqual( store.getCommentsBefore(TOKENS[2] /* "=" token */)[0].value, "B" ); }); it("should retrieve multiple comments before a node", () => { const comments = store.getCommentsBefore(CallExpression); assert.strictEqual(comments.length, 2); assert.strictEqual(comments[0].value, "E"); assert.strictEqual(comments[1].value, "F"); }); it("should retrieve comments before a Program node", () => { assert.strictEqual( store.getCommentsBefore(Program)[0].value, "A" ); }); it("should return an empty array if there are no comments before a node or token", () => { check( store.getCommentsBefore(BinaryExpression.right), [] ); check( store.getCommentsBefore(TOKENS[1]), [] ); }); }); describe("getCommentsAfter", () => { it("should retrieve comments after a node", () => { assert.strictEqual( store.getCommentsAfter(VariableDeclarator.id)[0].value, "B" ); }); it("should retrieve comments after a token", () => { assert.strictEqual( store.getCommentsAfter(TOKENS[2] /* "=" token */)[0].value, "C" ); }); it("should retrieve multiple comments after a node", () => { const comments = store.getCommentsAfter(VariableDeclaration); assert.strictEqual(comments.length, 2); assert.strictEqual(comments[0].value, "E"); assert.strictEqual(comments[1].value, "F"); }); it("should retrieve comments after a Program node", () => { assert.strictEqual( store.getCommentsAfter(Program)[0].value, "Z" ); }); it("should return an empty array if there are no comments after a node or token", () => { check( store.getCommentsAfter(CallExpression.callee), [] ); check( store.getCommentsAfter(TOKENS[0]), [] ); }); }); describe("getCommentsInside", () => { it("should retrieve comments inside a node", () => { check( store.getCommentsInside(Program), ["B", "C", "D", "E", "F"] ); check( store.getCommentsInside(VariableDeclaration), ["B", "C", "D"] ); check( store.getCommentsInside(VariableDeclarator), ["B", "C", "D"] ); check( store.getCommentsInside(BinaryExpression), ["D"] ); }); it("should return an empty array if a node does not contain any comments", () => { check( store.getCommentsInside(TOKENS[2]), [] ); }); }); });