diff --git a/apps/oxlint/src-js/plugins/source_code.ts b/apps/oxlint/src-js/plugins/source_code.ts
index 2f42a7bb7a365..ff17c5e6e6c57 100644
--- a/apps/oxlint/src-js/plugins/source_code.ts
+++ b/apps/oxlint/src-js/plugins/source_code.ts
@@ -226,8 +226,10 @@ export const SOURCE_CODE = Object.freeze({
getLastToken: tokenMethods.getLastToken,
getLastTokens: tokenMethods.getLastTokens,
getTokenBefore: tokenMethods.getTokenBefore,
+ getTokenOrCommentBefore: tokenMethods.getTokenOrCommentBefore,
getTokensBefore: tokenMethods.getTokensBefore,
getTokenAfter: tokenMethods.getTokenAfter,
+ getTokenOrCommentAfter: tokenMethods.getTokenOrCommentAfter,
getTokensAfter: tokenMethods.getTokensAfter,
getTokensBetween: tokenMethods.getTokensBetween,
getFirstTokenBetween: tokenMethods.getFirstTokenBetween,
@@ -236,6 +238,7 @@ export const SOURCE_CODE = Object.freeze({
getLastTokensBetween: tokenMethods.getLastTokensBetween,
getTokenByRangeStart: tokenMethods.getTokenByRangeStart,
isSpaceBetween: tokenMethods.isSpaceBetween,
+ isSpaceBetweenTokens: tokenMethods.isSpaceBetweenTokens,
});
export type SourceCode = typeof SOURCE_CODE;
diff --git a/apps/oxlint/src-js/plugins/tokens.ts b/apps/oxlint/src-js/plugins/tokens.ts
index abe8698b38c48..babecc55e8cb1 100644
--- a/apps/oxlint/src-js/plugins/tokens.ts
+++ b/apps/oxlint/src-js/plugins/tokens.ts
@@ -139,6 +139,24 @@ export function getTokenBefore(
}
/* oxlint-enable no-unused-vars */
+/**
+ * Get the token that precedes a given node or token.
+ *
+ * @deprecated Use `sourceCode.getTokenBefore` with `includeComments: true` instead.
+ *
+ * @param nodeOrToken The AST node or token.
+ * @param skip - Number of tokens to skip.
+ * @returns `Token`, or `null` if all were skipped.
+ */
+/* oxlint-disable no-unused-vars */
+export function getTokenOrCommentBefore(nodeOrToken: NodeOrToken | Comment, skip?: number): Token | null {
+ // TODO: Implement equivalent of:
+ // `return getTokenBefore(nodeOrToken, { includeComments: true, skip });`
+ // But could use a const object at top level for options object, to avoid creating temporary object on each call.
+ throw new Error('`sourceCode.getTokenOrCommentBefore` not implemented yet');
+}
+/* oxlint-enable no-unused-vars */
+
/**
* Get the tokens that precede a given node or token.
* @param nodeOrToken - The AST node or token.
@@ -169,6 +187,24 @@ export function getTokenAfter(
}
/* oxlint-enable no-unused-vars */
+/**
+ * Get the token that follows a given node or token.
+ *
+ * @deprecated Use `sourceCode.getTokenAfter` with `includeComments: true` instead.
+ *
+ * @param nodeOrToken The AST node or token.
+ * @param skip - Number of tokens to skip.
+ * @returns `Token`, or `null` if all were skipped.
+ */
+/* oxlint-disable no-unused-vars */
+export function getTokenOrCommentAfter(nodeOrToken: NodeOrToken | Comment, skip?: number): Token | null {
+ // TODO: Implement equivalent of:
+ // `return getTokenAfter(nodeOrToken, { includeComments: true, skip });`
+ // But could use a const object at top level for options object, to avoid creating temporary object on each call.
+ throw new Error('`sourceCode.getTokenOrCommentAfter` not implemented yet');
+}
+/* oxlint-enable no-unused-vars */
+
/**
* Get the tokens that follow a given node or token.
* @param nodeOrToken - The AST node or token.
@@ -346,3 +382,29 @@ export function isSpaceBetween(nodeOrToken1: NodeOrToken, nodeOrToken2: NodeOrTo
return WHITESPACE_REGEXP.test(sourceText.slice(gapStart, gapEnd));
}
+
+/**
+ * Determine if two nodes or tokens have at least one whitespace character between them.
+ * Order does not matter.
+ *
+ * Returns `false` if the given nodes or tokens overlap.
+ *
+ * Checks for whitespace *between tokens*, not including whitespace *inside tokens*.
+ * e.g. Returns `false` for `isSpaceBetween(x, y)` in `x+" "+y`.
+ *
+ * Unlike `SourceCode#isSpaceBetween`, this function does return `true` if there is a `JSText` token between the two
+ * input tokens, and it contains whitespace.
+ * e.g. Returns `true` for `isSpaceBetweenTokens(x, slash)` in `a b`.
+ *
+ * @deprecated Use `sourceCode.isSpaceBetween` instead.
+ *
+ * TODO: Implementation is not quite right at present, for same reasons as `SourceCode#isSpaceBetween`.
+ *
+ * @param nodeOrToken1 - The first node or token to check between.
+ * @param nodeOrToken2 - The second node or token to check between.
+ * @returns `true` if there is a whitespace character between
+ * any of the tokens found between the two given nodes or tokens.
+ */
+export function isSpaceBetweenTokens(token1: NodeOrToken, token2: NodeOrToken): boolean {
+ return isSpaceBetween(token1, token2);
+}
diff --git a/apps/oxlint/test/fixtures/isSpaceBetween/files/index.jsx b/apps/oxlint/test/fixtures/isSpaceBetween/files/index.jsx
new file mode 100644
index 0000000000000..074954418c862
--- /dev/null
+++ b/apps/oxlint/test/fixtures/isSpaceBetween/files/index.jsx
@@ -0,0 +1,10 @@
+aaa;
+
+// We should return `false` for `isSpaceBetween(openingElement, closingElement)`, but we currently return `true`
+b c;
+
+// We should return `false` for `isSpaceBetween(openingElement, closingElement)`, but we currently return `true`
+
+ d
+ e
+;
diff --git a/apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md b/apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md
index addf50f807e94..2143a93f24818 100644
--- a/apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md
+++ b/apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md
@@ -5,11 +5,17 @@
```
x test-plugin(is-space-between):
| isSpaceBetween(left, right): false
+ | isSpaceBetweenTokens(left, right): false
| isSpaceBetween(right, left): false
+ | isSpaceBetweenTokens(right, left): false
| isSpaceBetween(left, node): false
+ | isSpaceBetweenTokens(left, node): false
| isSpaceBetween(node, left): false
+ | isSpaceBetweenTokens(node, left): false
| isSpaceBetween(right, node): false
+ | isSpaceBetweenTokens(right, node): false
| isSpaceBetween(node, right): false
+ | isSpaceBetweenTokens(node, right): false
,-[files/index.js:2:1]
1 | // prettier-ignore
2 | noSpace=1;
@@ -19,7 +25,9 @@
x test-plugin(is-space-between):
| isSpaceBetween(leftExtended, right): false
+ | isSpaceBetweenTokens(leftExtended, right): false
| isSpaceBetween(right, leftExtended): false
+ | isSpaceBetweenTokens(right, leftExtended): false
,-[files/index.js:2:1]
1 | // prettier-ignore
2 | noSpace=1;
@@ -29,11 +37,17 @@
x test-plugin(is-space-between):
| isSpaceBetween(left, right): true
+ | isSpaceBetweenTokens(left, right): true
| isSpaceBetween(right, left): true
+ | isSpaceBetweenTokens(right, left): true
| isSpaceBetween(left, node): false
+ | isSpaceBetweenTokens(left, node): false
| isSpaceBetween(node, left): false
+ | isSpaceBetweenTokens(node, left): false
| isSpaceBetween(right, node): false
+ | isSpaceBetweenTokens(right, node): false
| isSpaceBetween(node, right): false
+ | isSpaceBetweenTokens(node, right): false
,-[files/index.js:5:1]
4 | // prettier-ignore
5 | singleSpaceBefore =2;
@@ -43,11 +57,17 @@
x test-plugin(is-space-between):
| isSpaceBetween(left, right): true
+ | isSpaceBetweenTokens(left, right): true
| isSpaceBetween(right, left): true
+ | isSpaceBetweenTokens(right, left): true
| isSpaceBetween(left, node): false
+ | isSpaceBetweenTokens(left, node): false
| isSpaceBetween(node, left): false
+ | isSpaceBetweenTokens(node, left): false
| isSpaceBetween(right, node): false
+ | isSpaceBetweenTokens(right, node): false
| isSpaceBetween(node, right): false
+ | isSpaceBetweenTokens(node, right): false
,-[files/index.js:8:1]
7 | // prettier-ignore
8 | singleSpaceAfter= 3;
@@ -57,11 +77,17 @@
x test-plugin(is-space-between):
| isSpaceBetween(left, right): true
+ | isSpaceBetweenTokens(left, right): true
| isSpaceBetween(right, left): true
+ | isSpaceBetweenTokens(right, left): true
| isSpaceBetween(left, node): false
+ | isSpaceBetweenTokens(left, node): false
| isSpaceBetween(node, left): false
+ | isSpaceBetweenTokens(node, left): false
| isSpaceBetween(right, node): false
+ | isSpaceBetweenTokens(right, node): false
| isSpaceBetween(node, right): false
+ | isSpaceBetweenTokens(node, right): false
,-[files/index.js:11:1]
10 | // prettier-ignore
11 | multipleSpaces = 4;
@@ -71,11 +97,17 @@
x test-plugin(is-space-between):
| isSpaceBetween(left, right): true
+ | isSpaceBetweenTokens(left, right): true
| isSpaceBetween(right, left): true
+ | isSpaceBetweenTokens(right, left): true
| isSpaceBetween(left, node): false
+ | isSpaceBetweenTokens(left, node): false
| isSpaceBetween(node, left): false
+ | isSpaceBetweenTokens(node, left): false
| isSpaceBetween(right, node): false
+ | isSpaceBetweenTokens(right, node): false
| isSpaceBetween(node, right): false
+ | isSpaceBetweenTokens(node, right): false
,-[files/index.js:14:1]
13 | // prettier-ignore
14 | ,-> newlineBefore=
@@ -85,11 +117,17 @@
x test-plugin(is-space-between):
| isSpaceBetween(left, right): true
+ | isSpaceBetweenTokens(left, right): true
| isSpaceBetween(right, left): true
+ | isSpaceBetweenTokens(right, left): true
| isSpaceBetween(left, node): false
+ | isSpaceBetweenTokens(left, node): false
| isSpaceBetween(node, left): false
+ | isSpaceBetweenTokens(node, left): false
| isSpaceBetween(right, node): false
+ | isSpaceBetweenTokens(right, node): false
| isSpaceBetween(node, right): false
+ | isSpaceBetweenTokens(node, right): false
,-[files/index.js:18:1]
17 | // prettier-ignore
18 | ,-> newlineAfter
@@ -99,7 +137,9 @@
x test-plugin(is-space-between):
| isSpaceBetween(node, binaryLeft): false
+ | isSpaceBetweenTokens(node, binaryLeft): false
| isSpaceBetween(binaryLeft, node): false
+ | isSpaceBetweenTokens(binaryLeft, node): false
,-[files/index.js:22:1]
21 | // prettier-ignore
22 | nested = 7 + 8;
@@ -109,11 +149,17 @@
x test-plugin(is-space-between):
| isSpaceBetween(left, right): true
+ | isSpaceBetweenTokens(left, right): true
| isSpaceBetween(right, left): true
+ | isSpaceBetweenTokens(right, left): true
| isSpaceBetween(left, node): false
+ | isSpaceBetweenTokens(left, node): false
| isSpaceBetween(node, left): false
+ | isSpaceBetweenTokens(node, left): false
| isSpaceBetween(right, node): false
+ | isSpaceBetweenTokens(right, node): false
| isSpaceBetween(node, right): false
+ | isSpaceBetweenTokens(node, right): false
,-[files/index.js:22:1]
21 | // prettier-ignore
22 | nested = 7 + 8;
@@ -123,15 +169,53 @@
x test-plugin(is-space-between):
| isSpaceBetween(beforeString, afterString): true
+ | isSpaceBetweenTokens(beforeString, afterString): true
| isSpaceBetween(afterString, beforeString): true
+ | isSpaceBetweenTokens(afterString, beforeString): true
,-[files/index.js:26:1]
25 | // prettier-ignore
26 | beforeString," ",afterString;
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
`----
-Found 0 warnings and 10 errors.
-Finished in Xms on 1 file using X threads.
+ x test-plugin(is-space-between):
+ | isSpaceBetween(openingElement, closingElement): false
+ | isSpaceBetweenTokens(openingElement, closingElement): false
+ | isSpaceBetween(closingElement, openingElement): false
+ | isSpaceBetweenTokens(closingElement, openingElement): false
+ ,-[files/index.jsx:1:1]
+ 1 | aaa;
+ : ^^^^^^^^^^^^^^
+ 2 |
+ `----
+
+ x test-plugin(is-space-between):
+ | isSpaceBetween(openingElement, closingElement): true
+ | isSpaceBetweenTokens(openingElement, closingElement): true
+ | isSpaceBetween(closingElement, openingElement): true
+ | isSpaceBetweenTokens(closingElement, openingElement): true
+ ,-[files/index.jsx:4:1]
+ 3 | // We should return `false` for `isSpaceBetween(openingElement, closingElement)`, but we currently return `true`
+ 4 | b c;
+ : ^^^^^^^^^^^^^^
+ 5 |
+ `----
+
+ x test-plugin(is-space-between):
+ | isSpaceBetween(openingElement, closingElement): true
+ | isSpaceBetweenTokens(openingElement, closingElement): true
+ | isSpaceBetween(closingElement, openingElement): true
+ | isSpaceBetweenTokens(closingElement, openingElement): true
+ ,-[files/index.jsx:7:1]
+ 6 | // We should return `false` for `isSpaceBetween(openingElement, closingElement)`, but we currently return `true`
+ 7 | ,->
+ 8 | | d
+ 9 | | e
+ 10 | `-> ;
+ `----
+
+Found 0 warnings and 13 errors.
+Finished in Xms on 2 files using X threads.
```
# stderr
diff --git a/apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts b/apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts
index 4a8c8063b4afd..a6f8cc02225a6 100644
--- a/apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts
+++ b/apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts
@@ -6,7 +6,7 @@ const testRule: Rule = {
create(context) {
return {
AssignmentExpression(node) {
- const { isSpaceBetween } = context.sourceCode,
+ const { isSpaceBetween, isSpaceBetweenTokens } = context.sourceCode,
{ left, right } = node;
context.report({
@@ -14,12 +14,18 @@ const testRule: Rule = {
'\n' +
// Test where 2 nodes are separated, maybe with whitespace in between
`isSpaceBetween(left, right): ${isSpaceBetween(left, right)}\n` +
+ `isSpaceBetweenTokens(left, right): ${isSpaceBetweenTokens(left, right)}\n` +
`isSpaceBetween(right, left): ${isSpaceBetween(right, left)}\n` +
+ `isSpaceBetweenTokens(right, left): ${isSpaceBetweenTokens(right, left)}\n` +
// Test where 1 node is inside another, sharing same `start` or `end`
`isSpaceBetween(left, node): ${isSpaceBetween(left, node)}\n` +
+ `isSpaceBetweenTokens(left, node): ${isSpaceBetweenTokens(left, node)}\n` +
`isSpaceBetween(node, left): ${isSpaceBetween(node, left)}\n` +
+ `isSpaceBetweenTokens(node, left): ${isSpaceBetweenTokens(node, left)}\n` +
`isSpaceBetween(right, node): ${isSpaceBetween(right, node)}\n` +
- `isSpaceBetween(node, right): ${isSpaceBetween(node, right)}`,
+ `isSpaceBetweenTokens(right, node): ${isSpaceBetweenTokens(right, node)}\n` +
+ `isSpaceBetween(node, right): ${isSpaceBetween(node, right)}\n` +
+ `isSpaceBetweenTokens(node, right): ${isSpaceBetweenTokens(node, right)}`,
node,
});
@@ -30,7 +36,9 @@ const testRule: Rule = {
message:
'\n' +
`isSpaceBetween(node, binaryLeft): ${isSpaceBetween(node, binaryLeft)}\n` +
- `isSpaceBetween(binaryLeft, node): ${isSpaceBetween(binaryLeft, node)}`,
+ `isSpaceBetweenTokens(node, binaryLeft): ${isSpaceBetweenTokens(node, binaryLeft)}\n` +
+ `isSpaceBetween(binaryLeft, node): ${isSpaceBetween(binaryLeft, node)}\n` +
+ `isSpaceBetweenTokens(binaryLeft, node): ${isSpaceBetweenTokens(binaryLeft, node)}`,
node,
});
}
@@ -47,14 +55,16 @@ const testRule: Rule = {
message:
'\n' +
`isSpaceBetween(leftExtended, right): ${isSpaceBetween(leftExtended, right)}\n` +
- `isSpaceBetween(right, leftExtended): ${isSpaceBetween(right, leftExtended)}`,
+ `isSpaceBetweenTokens(leftExtended, right): ${isSpaceBetweenTokens(leftExtended, right)}\n` +
+ `isSpaceBetween(right, leftExtended): ${isSpaceBetween(right, leftExtended)}\n` +
+ `isSpaceBetweenTokens(right, leftExtended): ${isSpaceBetweenTokens(right, leftExtended)}`,
node,
});
}
},
SequenceExpression(node) {
- const { isSpaceBetween } = context.sourceCode,
+ const { isSpaceBetween, isSpaceBetweenTokens } = context.sourceCode,
[beforeString, , afterString] = node.expressions;
// We get this wrong. Should be `false`, but we get `true`.
@@ -62,7 +72,27 @@ const testRule: Rule = {
message:
'\n' +
`isSpaceBetween(beforeString, afterString): ${isSpaceBetween(beforeString, afterString)}\n` +
- `isSpaceBetween(afterString, beforeString): ${isSpaceBetween(afterString, beforeString)}`,
+ `isSpaceBetweenTokens(beforeString, afterString): ${isSpaceBetweenTokens(beforeString, afterString)}\n` +
+ `isSpaceBetween(afterString, beforeString): ${isSpaceBetween(afterString, beforeString)}\n` +
+ `isSpaceBetweenTokens(afterString, beforeString): ${isSpaceBetweenTokens(afterString, beforeString)}`,
+ node,
+ });
+ },
+
+ JSXElement(node) {
+ const { isSpaceBetween, isSpaceBetweenTokens } = context.sourceCode,
+ { openingElement, closingElement } = node;
+
+ // We get this wrong.
+ // `isSpaceBetween` should return `false` for last 2 `JSXElement`s, but we get `true`.
+ // `isSpaceBetweenTokens` is correct for all cases.
+ context.report({
+ message:
+ '\n' +
+ `isSpaceBetween(openingElement, closingElement): ${isSpaceBetween(openingElement, closingElement)}\n` +
+ `isSpaceBetweenTokens(openingElement, closingElement): ${isSpaceBetweenTokens(openingElement, closingElement)}\n` +
+ `isSpaceBetween(closingElement, openingElement): ${isSpaceBetween(closingElement, openingElement)}\n` +
+ `isSpaceBetweenTokens(closingElement, openingElement): ${isSpaceBetweenTokens(closingElement, openingElement)}`,
node,
});
},