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
168 changes: 1 addition & 167 deletions apps/oxlint/src-js/plugins/comments.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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";

/**
Expand Down Expand Up @@ -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 */
175 changes: 175 additions & 0 deletions apps/oxlint/src-js/plugins/comments_methods.ts
Original file line number Diff line number Diff line change
@@ -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 */
2 changes: 1 addition & 1 deletion apps/oxlint/src-js/plugins/source_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
7 changes: 6 additions & 1 deletion apps/oxlint/tsdown_plugins/inline_search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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 };
},
},
Expand Down
Loading