Skip to content

feat: add support for getLocFromIndex and getIndexFromLoc #212

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 60 commits into
base: main
Choose a base branch
from

Conversation

lumirlumir
Copy link
Member

@lumirlumir lumirlumir commented May 24, 2025

Prerequisites checklist

What is the purpose of this pull request?

Hello,

In this PR, I've completed implemention of getLocFromIndex() and getIndexFromLoc().

I based the implementation on the current JavaScript SourceCode class and incorporated the fast lookup algorithm discussed in eslint/eslint#19782.

The logic of getLocFromIndex() and getIndexFromLoc() is nearly identical to that in the JavaScript SourceCode class, with the key difference being that it accounts for the lineStart and columnStart values initialized in the constructor.


lineStart and columnStart

TextSourceCodeBase should accept lineStart and columnStart, as these can vary depending on the language, and both getLocFromIndex() and getIndexFromLoc() rely on them for accurate calculations. Here are some examples:

What changes did you make? (Give an overview)

I've completed implemention of getLocFromIndex() and getIndexFromLoc().

Related Issues

fixes: #166
refs: eslint/markdown#376, eslint/css#167, eslint/json#109, eslint/eslint#19782

Is there anything you'd like reviewers to focus on?

Prerequisites

@nzakas nzakas moved this from Needs Triage to Implementing in Triage Jun 5, 2025
Copy link
Member Author

@lumirlumir lumirlumir left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It took a while, but I’ve finally finished implementing these two methods 😄

I’ve carefully reviewed the lazy calculation and addressed all the comments.

There might still be room for improvement—if so, I’d be happy to take a look.

(I didn’t go with the caching approach, as it turned out to be slightly slower in eslint/eslint#19782)

);
}

const rootNodeLoc = this.getLoc(this.ast);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now, we retrieve lineStart and columnStart information from the AST.

@lumirlumir lumirlumir requested a review from nzakas July 5, 2025 14:11
@lumirlumir lumirlumir marked this pull request as ready for review July 5, 2025 14:11
@lumirlumir lumirlumir marked this pull request as draft July 9, 2025 12:27
@lumirlumir
Copy link
Member Author

@nzakas

I've addressed all the comments and done a bit of refactoring to make the code clearer. 😄

@lumirlumir lumirlumir marked this pull request as ready for review July 10, 2025 06:33
*/
#parseText(text) {
// Create a new RegExp instance to avoid lastIndex issues.
const match = structuredClone(this.#lineEndingPattern).exec(text);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't seem like this is necessary, or at least, we should be able to reuse just one copy of the regex that is passed into the constructor. I don't think we need a new copy every time this function is called.

Also, the best way to duplicate a regex would be RegExp(pattern). structuredClone is really intended for cloning data structures not class instances.

* @param {string} text The source text to parse into lines.
* @returns {boolean} `true` if the text was successfully parsed into lines, `false` otherwise.
*/
#parseText(text) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't actually parsing text, so the name is misleading.

Maybe findNextLine is more accurate?

(Also, the JSDoc needs to be updated to reflect this.)

Boolean(additionalLinesNeeded--) &&
(match = lineEndingPattern.exec(text))
this.#parseText(this.text.slice(this.#lineStartIndices.at(-1))) &&
Boolean(additionalLinesNeeded--)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent here isn't very clear. Can we do this something like this?

Suggested change
Boolean(additionalLinesNeeded--)
additionalLinesNeeded-- > 0

Although, I think it would be even clearer if we could decrement inside the start of the loop instead of in the condition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Implementing
Development

Successfully merging this pull request may close these issues.

Change Request: Support getLocFromIndex() and getIndexFromLoc() methods for TextSourceCodeBase class
2 participants