Skip to content

Commit bc435a9

Browse files
mysticateaKai Cataldo
authored andcommitted
Fix: isSpaceBetweenTokens() recognizes spaces in JSXText (fixes #12614) (#12616)
* Fix: isSpaceBetween() recognizes spaces in JSXText (fixes #12614) * apply this fix only for isSpaceBetweenTokens() * move tests to the section for nodes * add tests for isSpaceBetween() * add tests for reversed order
1 parent 4928d51 commit bc435a9

File tree

2 files changed

+183
-25
lines changed

2 files changed

+183
-25
lines changed

lib/source-code/source-code.js

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,56 @@ function nodesOrTokensOverlap(first, second) {
9090
(second.range[0] <= first.range[0] && second.range[1] >= first.range[0]);
9191
}
9292

93+
/**
94+
* Determines if two nodes or tokens have at least one whitespace character
95+
* between them. Order does not matter. Returns false if the given nodes or
96+
* tokens overlap.
97+
* @param {SourceCode} sourceCode The source code object.
98+
* @param {ASTNode|Token} first The first node or token to check between.
99+
* @param {ASTNode|Token} second The second node or token to check between.
100+
* @param {boolean} checkInsideOfJSXText If `true` is present, check inside of JSXText tokens for backward compatibility.
101+
* @returns {boolean} True if there is a whitespace character between
102+
* any of the tokens found between the two given nodes or tokens.
103+
* @public
104+
*/
105+
function isSpaceBetween(sourceCode, first, second, checkInsideOfJSXText) {
106+
if (nodesOrTokensOverlap(first, second)) {
107+
return false;
108+
}
109+
110+
const [startingNodeOrToken, endingNodeOrToken] = first.range[1] <= second.range[0]
111+
? [first, second]
112+
: [second, first];
113+
const firstToken = sourceCode.getLastToken(startingNodeOrToken) || startingNodeOrToken;
114+
const finalToken = sourceCode.getFirstToken(endingNodeOrToken) || endingNodeOrToken;
115+
let currentToken = firstToken;
116+
117+
while (currentToken !== finalToken) {
118+
const nextToken = sourceCode.getTokenAfter(currentToken, { includeComments: true });
119+
120+
if (
121+
currentToken.range[1] !== nextToken.range[0] ||
122+
123+
/*
124+
* For backward compatibility, check speces in JSXText.
125+
* https://github.com/eslint/eslint/issues/12614
126+
*/
127+
(
128+
checkInsideOfJSXText &&
129+
nextToken !== finalToken &&
130+
nextToken.type === "JSXText" &&
131+
/\s/u.test(nextToken.value)
132+
)
133+
) {
134+
return true;
135+
}
136+
137+
currentToken = nextToken;
138+
}
139+
140+
return false;
141+
}
142+
93143
//------------------------------------------------------------------------------
94144
// Public Interface
95145
//------------------------------------------------------------------------------
@@ -433,42 +483,24 @@ class SourceCode extends TokenStore {
433483
* @public
434484
*/
435485
isSpaceBetween(first, second) {
436-
if (nodesOrTokensOverlap(first, second)) {
437-
return false;
438-
}
439-
440-
const [startingNodeOrToken, endingNodeOrToken] = first.range[1] <= second.range[0]
441-
? [first, second]
442-
: [second, first];
443-
const firstToken = this.getLastToken(startingNodeOrToken) || startingNodeOrToken;
444-
const finalToken = this.getFirstToken(endingNodeOrToken) || endingNodeOrToken;
445-
let currentToken = firstToken;
446-
447-
while (currentToken !== finalToken) {
448-
const nextToken = this.getTokenAfter(currentToken, { includeComments: true });
449-
450-
if (currentToken.range[1] !== nextToken.range[0]) {
451-
return true;
452-
}
453-
454-
currentToken = nextToken;
455-
}
456-
457-
return false;
486+
return isSpaceBetween(this, first, second, false);
458487
}
459488

460489
/**
461490
* Determines if two nodes or tokens have at least one whitespace character
462491
* between them. Order does not matter. Returns false if the given nodes or
463492
* tokens overlap.
464-
* @param {...ASTNode|Token} args The nodes or tokens to check between.
493+
* For backward compatibility, this method returns true if there are
494+
* `JSXText` tokens that contain whitespaces between the two.
495+
* @param {ASTNode|Token} first The first node or token to check between.
496+
* @param {ASTNode|Token} second The second node or token to check between.
465497
* @returns {boolean} True if there is a whitespace character between
466498
* any of the tokens found between the two given nodes or tokens.
467499
* @deprecated in favor of isSpaceBetween().
468500
* @public
469501
*/
470-
isSpaceBetweenTokens(...args) {
471-
return this.isSpaceBetween(...args);
502+
isSpaceBetweenTokens(first, second) {
503+
return isSpaceBetween(this, first, second, true);
472504
}
473505

474506
/**

tests/lib/source-code/source-code.js

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2076,6 +2076,69 @@ describe("SourceCode", () => {
20762076
});
20772077
});
20782078
});
2079+
2080+
it("JSXText tokens that contain only whitespaces should NOT be handled as space", () => {
2081+
const code = "let jsx = <div>\n {content}\n</div>";
2082+
const ast = espree.parse(code, { ...DEFAULT_CONFIG, ecmaFeatures: { jsx: true } });
2083+
const sourceCode = new SourceCode(code, ast);
2084+
const jsx = ast.body[0].declarations[0].init;
2085+
const interpolation = jsx.children[1];
2086+
2087+
assert.strictEqual(
2088+
sourceCode.isSpaceBetween(jsx.openingElement, interpolation),
2089+
false
2090+
);
2091+
assert.strictEqual(
2092+
sourceCode.isSpaceBetween(interpolation, jsx.closingElement),
2093+
false
2094+
);
2095+
2096+
// Reversed order
2097+
assert.strictEqual(
2098+
sourceCode.isSpaceBetween(interpolation, jsx.openingElement),
2099+
false
2100+
);
2101+
assert.strictEqual(
2102+
sourceCode.isSpaceBetween(jsx.closingElement, interpolation),
2103+
false
2104+
);
2105+
});
2106+
2107+
it("JSXText tokens that contain both letters and whitespaces should NOT be handled as space", () => {
2108+
const code = "let jsx = <div>\n Hello\n</div>";
2109+
const ast = espree.parse(code, { ...DEFAULT_CONFIG, ecmaFeatures: { jsx: true } });
2110+
const sourceCode = new SourceCode(code, ast);
2111+
const jsx = ast.body[0].declarations[0].init;
2112+
2113+
assert.strictEqual(
2114+
sourceCode.isSpaceBetween(jsx.openingElement, jsx.closingElement),
2115+
false
2116+
);
2117+
2118+
// Reversed order
2119+
assert.strictEqual(
2120+
sourceCode.isSpaceBetween(jsx.closingElement, jsx.openingElement),
2121+
false
2122+
);
2123+
});
2124+
2125+
it("JSXText tokens that contain only letters should NOT be handled as space", () => {
2126+
const code = "let jsx = <div>Hello</div>";
2127+
const ast = espree.parse(code, { ...DEFAULT_CONFIG, ecmaFeatures: { jsx: true } });
2128+
const sourceCode = new SourceCode(code, ast);
2129+
const jsx = ast.body[0].declarations[0].init;
2130+
2131+
assert.strictEqual(
2132+
sourceCode.isSpaceBetween(jsx.openingElement, jsx.closingElement),
2133+
false
2134+
);
2135+
2136+
// Reversed order
2137+
assert.strictEqual(
2138+
sourceCode.isSpaceBetween(jsx.closingElement, jsx.openingElement),
2139+
false
2140+
);
2141+
});
20792142
});
20802143

20812144
describe("should return false either of the arguments' location is inside the other one", () => {
@@ -2409,6 +2472,69 @@ describe("SourceCode", () => {
24092472
});
24102473
});
24112474
});
2475+
2476+
it("JSXText tokens that contain only whitespaces should be handled as space", () => {
2477+
const code = "let jsx = <div>\n {content}\n</div>";
2478+
const ast = espree.parse(code, { ...DEFAULT_CONFIG, ecmaFeatures: { jsx: true } });
2479+
const sourceCode = new SourceCode(code, ast);
2480+
const jsx = ast.body[0].declarations[0].init;
2481+
const interpolation = jsx.children[1];
2482+
2483+
assert.strictEqual(
2484+
sourceCode.isSpaceBetweenTokens(jsx.openingElement, interpolation),
2485+
true
2486+
);
2487+
assert.strictEqual(
2488+
sourceCode.isSpaceBetweenTokens(interpolation, jsx.closingElement),
2489+
true
2490+
);
2491+
2492+
// Reversed order
2493+
assert.strictEqual(
2494+
sourceCode.isSpaceBetweenTokens(interpolation, jsx.openingElement),
2495+
true
2496+
);
2497+
assert.strictEqual(
2498+
sourceCode.isSpaceBetweenTokens(jsx.closingElement, interpolation),
2499+
true
2500+
);
2501+
});
2502+
2503+
it("JSXText tokens that contain both letters and whitespaces should be handled as space", () => {
2504+
const code = "let jsx = <div>\n Hello\n</div>";
2505+
const ast = espree.parse(code, { ...DEFAULT_CONFIG, ecmaFeatures: { jsx: true } });
2506+
const sourceCode = new SourceCode(code, ast);
2507+
const jsx = ast.body[0].declarations[0].init;
2508+
2509+
assert.strictEqual(
2510+
sourceCode.isSpaceBetweenTokens(jsx.openingElement, jsx.closingElement),
2511+
true
2512+
);
2513+
2514+
// Reversed order
2515+
assert.strictEqual(
2516+
sourceCode.isSpaceBetweenTokens(jsx.closingElement, jsx.openingElement),
2517+
true
2518+
);
2519+
});
2520+
2521+
it("JSXText tokens that contain only letters should NOT be handled as space", () => {
2522+
const code = "let jsx = <div>Hello</div>";
2523+
const ast = espree.parse(code, { ...DEFAULT_CONFIG, ecmaFeatures: { jsx: true } });
2524+
const sourceCode = new SourceCode(code, ast);
2525+
const jsx = ast.body[0].declarations[0].init;
2526+
2527+
assert.strictEqual(
2528+
sourceCode.isSpaceBetweenTokens(jsx.openingElement, jsx.closingElement),
2529+
false
2530+
);
2531+
2532+
// Reversed order
2533+
assert.strictEqual(
2534+
sourceCode.isSpaceBetweenTokens(jsx.closingElement, jsx.openingElement),
2535+
false
2536+
);
2537+
});
24122538
});
24132539

24142540
describe("should return false either of the arguments' location is inside the other one", () => {

0 commit comments

Comments
 (0)