Files
oxc-project__oxc/apps/oxlint/test/isSpaceBetween.test.ts

215 lines
8.5 KiB
TypeScript

import assert from "node:assert";
import { join as pathJoin } from "node:path";
import { describe, it, expect, beforeEach } from "vitest";
import { parse as parseRaw } from "../src-js/package/parse.ts";
import { setupFileContext, resetFileContext } from "../src-js/plugins/context.ts";
import { buffers } from "../src-js/plugins/lint.ts";
import {
ast,
initAst,
resetSourceAndAst,
setupSourceForFile,
} from "../src-js/plugins/source_code.ts";
import { isSpaceBetween, isSpaceBetweenTokens } from "../src-js/plugins/tokens_methods.ts";
import { debugAssertIsNonNull } from "../src-js/utils/asserts.ts";
import type { ParseOptions } from "../src-js/package/parse.ts";
import type { Program } from "../src-js/generated/types.d.ts";
/**
* Parse source text into AST using Oxc parser.
* Set up global state, as if was linting the provided file.
* @param filename - Filename
* @param sourceText - Source text
* @param options - Parse options
* @returns AST
*/
function parse(filename: string, sourceText: string, options?: ParseOptions): Program {
// Set file path
const path = pathJoin(import.meta.dirname, filename);
setupFileContext(path);
// Parse source, writing source text and AST into buffer
parseRaw(path, sourceText, options);
// Set buffer (`parseRaw` adds buffer containing AST to `buffers` at index 0)
const buffer = buffers[0];
debugAssertIsNonNull(buffer);
setupSourceForFile(buffer, /* hasBOM */ false);
// Deserialize AST from buffer
initAst();
debugAssertIsNonNull(ast);
// Return AST
return ast;
}
beforeEach(() => {
resetFileContext();
resetSourceAndAst();
});
describe("isSpaceBetween()", () => {
// https://github.com/eslint/eslint/blob/df5566f826d9f5740546e473aa6876b1f7d2f12c/tests/lib/languages/js/source-code/source-code.js#L721-L745
describe("should return true when there is at least one whitespace character between two nodes", () => {
for (const [code, expected] of [
["let foo = bar;let baz = qux;", false],
["let foo = bar;/**/let baz = qux;", false],
["let foo = bar;/* */let baz = qux;", false],
["let foo = bar; let baz = qux;", true],
["let foo = bar; /**/let baz = qux;", true],
["let foo = bar; /* */let baz = qux;", true],
["let foo = bar;/**/ let baz = qux;", true],
["let foo = bar;/* */ let baz = qux;", true],
["let foo = bar; /**/ let baz = qux;", true],
["let foo = bar; /* */ let baz = qux;", true],
["let foo = bar;\tlet baz = qux;", true],
["let foo = bar;\t/**/let baz = qux;", true],
["let foo = bar;\t/* */let baz = qux;", true],
["let foo = bar;/**/\tlet baz = qux;", true],
["let foo = bar;/* */\tlet baz = qux;", true],
["let foo = bar;\t/**/\tlet baz = qux;", true],
["let foo = bar;\t/* */\tlet baz = qux;", true],
["let foo = bar;\nlet baz = qux;", true],
["let foo = bar;\n/**/let baz = qux;", true],
["let foo = bar;\n/* */let baz = qux;", true],
["let foo = bar;/**/\nlet baz = qux;", true],
["let foo = bar;/* */\nlet baz = qux;", true],
["let foo = bar;\n/**/\nlet baz = qux;", true],
["let foo = bar;\n/* */\nlet baz = qux;", true],
["let foo = 1;let foo2 = 2; let foo3 = 3;", true],
] satisfies [string, boolean][]) {
it(`should return ${expected} for ${code}`, () => {
const ast = parse("dummy.js", code);
const firstStmt = ast.body[0];
const lastStmt = ast.body.at(-1);
assert(firstStmt != null);
assert(lastStmt != null);
assert(firstStmt !== lastStmt);
expect(isSpaceBetween(firstStmt, lastStmt)).toBe(expected);
// Reversed order
expect(isSpaceBetween(lastStmt, firstStmt)).toBe(expected);
});
}
});
});
describe("isSpaceBetweenTokens()", () => {
// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/source-code.js#L2166-L2206
it("JSXText tokens that contain only whitespaces should be handled as space", () => {
const ast = parse("dummy.jsx", "let jsx = <div>\n {content}\n</div>");
const stmt = ast.body[0];
assert.strictEqual(stmt.type, "VariableDeclaration");
const jsx = stmt.declarations[0].init!;
assert.strictEqual(jsx.type, "JSXElement");
const { openingElement, closingElement } = jsx;
assert(closingElement !== null);
const interpolation = jsx.children[1];
assert(interpolation != null);
expect(isSpaceBetweenTokens(openingElement, interpolation)).toBe(true);
expect(isSpaceBetweenTokens(interpolation, closingElement)).toBe(true);
// Reversed order
expect(isSpaceBetweenTokens(interpolation, openingElement)).toBe(true);
expect(isSpaceBetweenTokens(closingElement, interpolation)).toBe(true);
});
// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/source-code.js#L2208-L2233
it("JSXText tokens that contain both letters and whitespaces should be handled as space", () => {
const ast = parse("dummy.jsx", "let jsx = <div>\n Hello\n</div>");
const stmt = ast.body[0];
assert.strictEqual(stmt.type, "VariableDeclaration");
const jsx = stmt.declarations[0].init!;
assert.strictEqual(jsx.type, "JSXElement");
const { openingElement, closingElement } = jsx;
assert(closingElement !== null);
expect(isSpaceBetweenTokens(openingElement, closingElement)).toBe(true);
// Reversed order
expect(isSpaceBetweenTokens(closingElement, openingElement)).toBe(true);
});
// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/source-code.js#L2235-L2261
it("JSXText tokens that contain only letters should NOT be handled as space", () => {
const ast = parse("dummy.jsx", "let jsx = <div>Hello</div>");
const stmt = ast.body[0];
assert.strictEqual(stmt.type, "VariableDeclaration");
const jsx = stmt.declarations[0].init!;
assert.strictEqual(jsx.type, "JSXElement");
const { openingElement, closingElement } = jsx;
assert(closingElement !== null);
expect(isSpaceBetweenTokens(openingElement, closingElement)).toBe(false);
// Reversed order
expect(isSpaceBetweenTokens(closingElement, openingElement)).toBe(false);
});
// https://github.com/oxc-project/oxc/issues/21306
it("JSX attribute string literal with spaces should NOT be treated as space between tokens", () => {
const ast = parse("dummy.jsx", '<Foo className="email-compose field-label" />');
const stmt = ast.body[0];
assert.strictEqual(stmt.type, "ExpressionStatement");
const jsx = stmt.expression;
assert.strictEqual(jsx.type, "JSXElement");
const attr = jsx.openingElement.attributes[0];
assert.strictEqual(attr.type, "JSXAttribute");
assert(attr.value !== null);
const { tokens } = ast;
const equalToken = tokens.find((t: { value: string }) => t.value === "=");
assert(equalToken != null);
expect(isSpaceBetweenTokens(equalToken, attr.value)).toBe(false);
// Reversed order
expect(isSpaceBetweenTokens(attr.value, equalToken)).toBe(false);
});
// https://github.com/oxc-project/oxc/issues/21306
it("JSX attribute string literal with spaces and actual space before it should be treated as space", () => {
const ast = parse("dummy.jsx", '<Foo className= "email-compose field-label" />');
const stmt = ast.body[0];
assert.strictEqual(stmt.type, "ExpressionStatement");
const jsx = stmt.expression;
assert.strictEqual(jsx.type, "JSXElement");
const attr = jsx.openingElement.attributes[0];
assert.strictEqual(attr.type, "JSXAttribute");
assert(attr.value !== null);
const { tokens } = ast;
const equalToken = tokens.find((t: { value: string }) => t.value === "=");
assert(equalToken != null);
expect(isSpaceBetweenTokens(equalToken, attr.value)).toBe(true);
// Reversed order
expect(isSpaceBetweenTokens(attr.value, equalToken)).toBe(true);
});
// https://github.com/eslint/eslint/blob/v9.39.1/tests/lib/languages/js/source-code/source-code.js#L2263-L2300
it("should return false if either of the arguments' location is inside the other one", () => {
const ast = parse("dummy.js", "let foo = bar;");
const stmt = ast.body[0];
assert(stmt != null);
const firstToken = ast.tokens[0];
const lastToken = ast.tokens.at(-1);
assert(firstToken != null);
assert(lastToken != null);
assert(firstToken !== lastToken);
expect(isSpaceBetweenTokens(firstToken, stmt)).toBe(false);
expect(isSpaceBetweenTokens(lastToken, stmt)).toBe(false);
// Reversed order
expect(isSpaceBetweenTokens(stmt, firstToken)).toBe(false);
expect(isSpaceBetweenTokens(stmt, lastToken)).toBe(false);
});
});