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, }); },