diff --git a/apps/oxlint/src-js/plugins/comments.ts b/apps/oxlint/src-js/plugins/comments.ts index 5d9e98211f973..7535d4773d98d 100644 --- a/apps/oxlint/src-js/plugins/comments.ts +++ b/apps/oxlint/src-js/plugins/comments.ts @@ -1,5 +1,5 @@ /* - * Comment class, object pooling, deserialization, and `SourceCode` methods related to comments. + * Comment class, object pooling, and deserialization. */ import { ast, buffer, initAst, sourceText } from "./source_code.ts"; @@ -12,10 +12,8 @@ import { DATA_POINTER_POS_32, } from "../generated/constants.ts"; import { computeLoc } from "./location.ts"; -import { firstTokenAtOrAfter } from "./tokens_methods.ts"; import { debugAssert, debugAssertIsNonNull } from "../utils/asserts.ts"; -import type { Node, NodeOrToken } from "./types.ts"; import type { Location, Span } from "./location.ts"; /** @@ -195,167 +193,3 @@ export function resetComments(): void { comments = null; } - -// Regex that tests if a string is entirely whitespace. -const WHITESPACE_ONLY_REGEXP = /^\s*$/; - -/** - * Retrieve an array containing all comments in the source code. - * @returns Array of `Comment`s in order they appear in source. - */ -export function getAllComments(): CommentType[] { - if (comments === null) initComments(); - debugAssertIsNonNull(comments); - return comments; -} - -/** - * Get all comments directly before the given node or token. - * - * "Directly before" means only comments before this node, and after the preceding token. - * - * ```js - * // Define `x` - * const x = 1; - * // Define `y` - * const y = 2; - * ``` - * - * `sourceCode.getCommentsBefore(varDeclY)` will only return "Define `y`" comment, not also "Define `x`". - * - * @param nodeOrToken - The AST node or token to check for adjacent comment tokens. - * @returns Array of `Comment`s in occurrence order. - */ -export function getCommentsBefore(nodeOrToken: NodeOrToken): CommentType[] { - if (comments === null) initComments(); - debugAssertIsNonNull(comments); - debugAssertIsNonNull(sourceText); - - let targetStart = nodeOrToken.range[0]; // start - - // Binary search for first comment at or past `nodeOrToken`'s start. - // Comments before this index are candidates to be included in returned array. - const sliceEnd = firstTokenAtOrAfter(comments, targetStart, 0); - - let sliceStart = comments.length; - for (let i = sliceEnd - 1; i >= 0; i--) { - const comment = comments[i]; - const gap = sourceText.slice(comment.end, targetStart); - // Ensure that there is nothing except whitespace between the end of the - // current comment and the start of the next one as we iterate backwards - if (WHITESPACE_ONLY_REGEXP.test(gap)) { - sliceStart = i; - targetStart = comment.start; - } else { - break; - } - } - - return comments.slice(sliceStart, sliceEnd); -} - -/** - * Get all comment tokens directly after the given node or token. - * - * "Directly after" means only comments between end of this node, and the next token following it. - * - * ```js - * const x = 1; - * // Define `y` - * const y = 2; - * // Define `z` - * const z = 3; - * ``` - * - * `sourceCode.getCommentsAfter(varDeclX)` will only return "Define `y`" comment, not also "Define `z`". - * - * @param nodeOrToken - The AST node or token to check for adjacent comment tokens. - * @returns Array of `Comment`s in occurrence order. - */ -export function getCommentsAfter(nodeOrToken: NodeOrToken): CommentType[] { - if (comments === null) initComments(); - debugAssertIsNonNull(comments); - debugAssertIsNonNull(sourceText); - - let targetEnd = nodeOrToken.range[1]; // end - - // Binary search for first comment at or past `nodeOrToken`'s end. - // Comments from this index onwards are candidates to be included in returned array. - const sliceStart = firstTokenAtOrAfter(comments, targetEnd, 0); - - const commentsLength = comments.length; - let sliceEnd = 0; - for (let i = sliceStart; i < commentsLength; i++) { - // Ensure that there is nothing except whitespace between the - // end of the previous comment and the start of the current one - const comment = comments[i]; - const gap = sourceText.slice(targetEnd, comment.start); - if (WHITESPACE_ONLY_REGEXP.test(gap)) { - sliceEnd = i + 1; - targetEnd = comment.end; - } else { - break; - } - } - - return comments.slice(sliceStart, sliceEnd); -} - -/** - * Get all comment tokens inside the given node. - * @param node - The AST node to get the comments for. - * @returns Array of `Comment`s in occurrence order. - */ -export function getCommentsInside(node: Node): CommentType[] { - if (comments === null) initComments(); - debugAssertIsNonNull(comments); - - const { range } = node, - rangeStart = range[0], - rangeEnd = range[1]; - - // Binary search for first comment within `node`'s range - const sliceStart = firstTokenAtOrAfter(comments, rangeStart, 0); - // Binary search for first comment outside `node`'s range. - // Its index is used as `sliceEnd`, which is exclusive of the slice. - const sliceEnd = firstTokenAtOrAfter(comments, rangeEnd, sliceStart); - - return comments.slice(sliceStart, sliceEnd); -} - -/** - * Check whether any comments exist or not between the given 2 nodes. - * @param nodeOrToken1 - Start node/token. - * @param nodeOrToken2 - End node/token. - * @returns `true` if one or more comments exist between the two. - */ -export function commentsExistBetween( - nodeOrToken1: NodeOrToken, - nodeOrToken2: NodeOrToken, -): boolean { - if (comments === null) initComments(); - debugAssertIsNonNull(comments); - - // Find the first comment after `nodeOrToken1` ends. - const betweenRangeStart = nodeOrToken1.range[1]; - const firstCommentBetween = firstTokenAtOrAfter(comments, betweenRangeStart, 0); - // Check if it ends before `nodeOrToken2` starts. - return ( - firstCommentBetween < comments.length && - comments[firstCommentBetween].end <= nodeOrToken2.range[0] - ); -} - -/** - * Retrieve the JSDoc comment for a given node. - * - * @deprecated - * - * @param node - The AST node to get the comment for. - * @returns The JSDoc comment for the given node, or `null` if not found. - */ -/* oxlint-disable no-unused-vars */ -export function getJSDocComment(node: Node): CommentType | null { - throw new Error("`sourceCode.getJSDocComment` is not supported at present (and deprecated)"); // TODO -} -/* oxlint-enable no-unused-vars */ diff --git a/apps/oxlint/src-js/plugins/comments_methods.ts b/apps/oxlint/src-js/plugins/comments_methods.ts new file mode 100644 index 0000000000000..ffbff174fc314 --- /dev/null +++ b/apps/oxlint/src-js/plugins/comments_methods.ts @@ -0,0 +1,175 @@ +/* + * `SourceCode` methods related to comments. + */ + +import { comments, initComments } from "./comments.ts"; +import { sourceText } from "./source_code.ts"; +import { firstTokenAtOrAfter } from "./tokens_methods.ts"; +import { debugAssertIsNonNull } from "../utils/asserts.ts"; + +import type { Comment } from "./comments.ts"; +import type { Node, NodeOrToken } from "./types.ts"; + +// Regex that tests if a string is entirely whitespace. +const WHITESPACE_ONLY_REGEXP = /^\s*$/; + +/** + * Retrieve an array containing all comments in the source code. + * @returns Array of `Comment`s in order they appear in source. + */ +export function getAllComments(): Comment[] { + if (comments === null) initComments(); + debugAssertIsNonNull(comments); + return comments; +} + +/** + * Get all comments directly before the given node or token. + * + * "Directly before" means only comments before this node, and after the preceding token. + * + * ```js + * // Define `x` + * const x = 1; + * // Define `y` + * const y = 2; + * ``` + * + * `sourceCode.getCommentsBefore(varDeclY)` will only return "Define `y`" comment, not also "Define `x`". + * + * @param nodeOrToken - The AST node or token to check for adjacent comment tokens. + * @returns Array of `Comment`s in occurrence order. + */ +export function getCommentsBefore(nodeOrToken: NodeOrToken): Comment[] { + if (comments === null) initComments(); + debugAssertIsNonNull(comments); + debugAssertIsNonNull(sourceText); + + let targetStart = nodeOrToken.range[0]; // start + + // Binary search for first comment at or past `nodeOrToken`'s start. + // Comments before this index are candidates to be included in returned array. + const sliceEnd = firstTokenAtOrAfter(comments, targetStart, 0); + + let sliceStart = comments.length; + for (let i = sliceEnd - 1; i >= 0; i--) { + const comment = comments[i]; + const gap = sourceText.slice(comment.end, targetStart); + // Ensure that there is nothing except whitespace between the end of the + // current comment and the start of the next one as we iterate backwards + if (WHITESPACE_ONLY_REGEXP.test(gap)) { + sliceStart = i; + targetStart = comment.start; + } else { + break; + } + } + + return comments.slice(sliceStart, sliceEnd); +} + +/** + * Get all comment tokens directly after the given node or token. + * + * "Directly after" means only comments between end of this node, and the next token following it. + * + * ```js + * const x = 1; + * // Define `y` + * const y = 2; + * // Define `z` + * const z = 3; + * ``` + * + * `sourceCode.getCommentsAfter(varDeclX)` will only return "Define `y`" comment, not also "Define `z`". + * + * @param nodeOrToken - The AST node or token to check for adjacent comment tokens. + * @returns Array of `Comment`s in occurrence order. + */ +export function getCommentsAfter(nodeOrToken: NodeOrToken): Comment[] { + if (comments === null) initComments(); + debugAssertIsNonNull(comments); + debugAssertIsNonNull(sourceText); + + let targetEnd = nodeOrToken.range[1]; // end + + // Binary search for first comment at or past `nodeOrToken`'s end. + // Comments from this index onwards are candidates to be included in returned array. + const sliceStart = firstTokenAtOrAfter(comments, targetEnd, 0); + + const commentsLength = comments.length; + let sliceEnd = 0; + for (let i = sliceStart; i < commentsLength; i++) { + // Ensure that there is nothing except whitespace between the + // end of the previous comment and the start of the current one + const comment = comments[i]; + const gap = sourceText.slice(targetEnd, comment.start); + if (WHITESPACE_ONLY_REGEXP.test(gap)) { + sliceEnd = i + 1; + targetEnd = comment.end; + } else { + break; + } + } + + return comments.slice(sliceStart, sliceEnd); +} + +/** + * Get all comment tokens inside the given node. + * @param node - The AST node to get the comments for. + * @returns Array of `Comment`s in occurrence order. + */ +export function getCommentsInside(node: Node): Comment[] { + if (comments === null) initComments(); + debugAssertIsNonNull(comments); + + const { range } = node, + rangeStart = range[0], + rangeEnd = range[1]; + + // Binary search for first comment within `node`'s range + const sliceStart = firstTokenAtOrAfter(comments, rangeStart, 0); + // Binary search for first comment outside `node`'s range. + // Its index is used as `sliceEnd`, which is exclusive of the slice. + const sliceEnd = firstTokenAtOrAfter(comments, rangeEnd, sliceStart); + + return comments.slice(sliceStart, sliceEnd); +} + +/** + * Check whether any comments exist or not between the given 2 nodes. + * @param nodeOrToken1 - Start node/token. + * @param nodeOrToken2 - End node/token. + * @returns `true` if one or more comments exist between the two. + */ +export function commentsExistBetween( + nodeOrToken1: NodeOrToken, + nodeOrToken2: NodeOrToken, +): boolean { + if (comments === null) initComments(); + debugAssertIsNonNull(comments); + + // Find the first comment after `nodeOrToken1` ends. + const betweenRangeStart = nodeOrToken1.range[1]; + const firstCommentBetween = firstTokenAtOrAfter(comments, betweenRangeStart, 0); + // Check if it ends before `nodeOrToken2` starts. + return ( + firstCommentBetween < comments.length && + comments[firstCommentBetween].end <= nodeOrToken2.range[0] + ); +} + +/** + * Retrieve the JSDoc comment for a given node. + * + * @deprecated + * + * @param node - The AST node to get the comment for. + * @returns The JSDoc comment for the given node, or `null` if not found. + */ +/* oxlint-disable no-unused-vars */ +export function getJSDocComment(node: Node): Comment | null { + throw new Error("`sourceCode.getJSDocComment` is not supported at present (and deprecated)"); // TODO +} +/* oxlint-enable no-unused-vars */ diff --git a/apps/oxlint/src-js/plugins/source_code.ts b/apps/oxlint/src-js/plugins/source_code.ts index 49f1b092b7352..9dc10fb8f990d 100644 --- a/apps/oxlint/src-js/plugins/source_code.ts +++ b/apps/oxlint/src-js/plugins/source_code.ts @@ -12,7 +12,7 @@ import { deserializeProgramOnly, resetBuffer } from "../generated/deserialize.js import visitorKeys from "../generated/keys.ts"; import { resetComments } from "./comments.ts"; -import * as commentMethods from "./comments.ts"; +import * as commentMethods from "./comments_methods.ts"; import { ecmaVersion } from "./context.ts"; import * as locationMethods from "./location.ts"; import { getNodeLoc, initLines, lines, lineStartIndices, resetLines } from "./location.ts"; diff --git a/apps/oxlint/tsdown_plugins/inline_search.ts b/apps/oxlint/tsdown_plugins/inline_search.ts index e6afc6810a671..e9e2cb4068343 100644 --- a/apps/oxlint/tsdown_plugins/inline_search.ts +++ b/apps/oxlint/tsdown_plugins/inline_search.ts @@ -13,7 +13,7 @@ const INLINE_FUNC_NAME = "firstTokenAtOrAfter"; const INLINE_FUNC_PATH = pathJoin(import.meta.dirname, "../src-js/plugins/tokens_methods.ts"); // Files to inline the binary search function into -const FILES = ["/src-js/plugins/tokens_methods.ts", "/src-js/plugins/comments.ts"]; +const FILES = ["/src-js/plugins/tokens_methods.ts", "/src-js/plugins/comments_methods.ts"]; // Get details of the function to be inlined const { fnParams, returnParamIndex, fnBodySource } = extractInlinedFunction( @@ -151,6 +151,11 @@ const plugin: Plugin = { }); visitor.visit(program); + // Check some calls were inlined. If there weren't, probably paths in `FILES` are wrong. + if (inlinedCallExprs.size === 0) { + throw new Error(`No \`${INLINE_FUNC_NAME}\` calls found in ${path}`); + } + return { code: magicString }; }, },