Skip to content
Closed
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
49 changes: 47 additions & 2 deletions apps/oxlint/src-js/plugins/source_code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ export function getAst(): Program {
* Split source text into lines.
*/
function initLines(): void {
if (lines.length !== 0) return;

if (sourceText === null) initSourceText();

// This implementation is based on the one in ESLint.
Expand Down Expand Up @@ -174,7 +176,7 @@ export const SOURCE_CODE = Object.freeze({

// Get source text as array of lines, split according to specification's definition of line breaks.
get lines(): string[] {
if (lines.length === 0) initLines();
initLines();
return lines;
},

Expand Down Expand Up @@ -525,7 +527,50 @@ export const SOURCE_CODE = Object.freeze({
*/
// oxlint-disable-next-line no-unused-vars
getIndexFromLoc(loc: LineColumn): number {
throw new Error('`sourceCode.getIndexFromLoc` not implemented yet'); // TODO
if (loc !== null && typeof loc === 'object') {
const { line, column } = loc;
if (typeof line === 'number' && typeof column === 'number') {
initLines();

const linesCount = lineStartOffsets.length;
if (line <= 0 || line > linesCount) {
throw new RangeError(
`Line number out of range (line ${line} requested). ` +
`Line numbers should be 1-based, and less than or equal to number of lines in file (${linesCount}).`,
);
}
if (column < 0) throw new RangeError(`Invalid column number (column ${column} requested).`);

const lineStartOffset = lineStartOffsets[line - 1];
const offset = lineStartOffset + column;

// Comment from ESLint implementation:
/*
* By design, `getIndexFromLoc({ line: lineNum, column: 0 })` should return the start index of
* the given line, provided that the line number is valid element of `lines`. Since the
* last element of `lines` is an empty string for files with trailing newlines, add a
* special case where getting the index for the first location after the end of the file
* will return the length of the file, rather than throwing an error. This allows rules to
* use `getIndexFromLoc` consistently without worrying about edge cases at the end of a file.
*/

let lineEndOffset;
if (line === linesCount) {
Copy link

Copilot AI Oct 2, 2025

Choose a reason for hiding this comment

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

Missing null check for sourceText. The code calls initLines() which may call initSourceText(), but there's no guarantee that sourceText is not null at this point. Add a null check or ensure sourceText is properly initialized.

Suggested change
if (line === linesCount) {
if (line === linesCount) {
if (sourceText == null) {
throw new TypeError("sourceText is null or undefined when accessing its length.");
}

Copilot uses AI. Check for mistakes.
lineEndOffset = sourceText.length;
if (offset <= lineEndOffset) return offset;
} else {
Copy link

Copilot AI Oct 2, 2025

Choose a reason for hiding this comment

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

Potential array bounds error. The code accesses lineStartOffsets[line] where line could equal linesCount, but lineStartOffsets may only have linesCount elements (0-indexed). This could cause an out-of-bounds access when line === linesCount.

Suggested change
} else {
} else {
// line is guaranteed to be < linesCount here, so lineStartOffsets[line] is in bounds
console.assert(line < linesCount, "lineStartOffsets[line] out of bounds");

Copilot uses AI. Check for mistakes.
lineEndOffset = lineStartOffsets[line];
if (offset < lineEndOffset) return offset;
}

throw new RangeError(
`Column number out of range (column ${column} requested, ` +
`but the length of line ${line} is ${lineEndOffset - lineStartOffset}).`,
);
}
}

throw new TypeError('Expected `loc` to be an object with numeric `line` and `column` properties.');
},

/**
Expand Down
Loading