Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions apps/oxlint/src-js/plugins/source_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
62 changes: 62 additions & 0 deletions apps/oxlint/src-js/plugins/tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 `<X>a b</X>`.
*
* @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);
}
10 changes: 10 additions & 0 deletions apps/oxlint/test/fixtures/isSpaceBetween/files/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Foo>aaa</Foo>;

// We should return `false` for `isSpaceBetween(openingElement, closingElement)`, but we currently return `true`
<Bar>b c</Bar>;

// We should return `false` for `isSpaceBetween(openingElement, closingElement)`, but we currently return `true`
<Qux>
d
e
</Qux>;
88 changes: 86 additions & 2 deletions apps/oxlint/test/fixtures/isSpaceBetween/output.snap.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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=
Expand All @@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand All @@ -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 | <Foo>aaa</Foo>;
: ^^^^^^^^^^^^^^
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 | <Bar>b c</Bar>;
: ^^^^^^^^^^^^^^
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 | ,-> <Qux>
8 | | d
9 | | e
10 | `-> </Qux>;
`----

Found 0 warnings and 13 errors.
Finished in Xms on 2 files using X threads.
```

# stderr
Expand Down
42 changes: 36 additions & 6 deletions apps/oxlint/test/fixtures/isSpaceBetween/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,26 @@ const testRule: Rule = {
create(context) {
return {
AssignmentExpression(node) {
const { isSpaceBetween } = context.sourceCode,
const { isSpaceBetween, isSpaceBetweenTokens } = context.sourceCode,
{ left, right } = node;

context.report({
message:
'\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,
});

Expand All @@ -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,
});
}
Expand All @@ -47,22 +55,44 @@ 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`.
context.report({
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,
});
},
Expand Down
Loading