@@ -8,18 +8,16 @@ import {
88// @ts -expect-error we need to generate `.d.ts` file for this module
99// We use the deserializer which removes `ParenthesizedExpression`s from AST to match ESLint
1010import { deserializeProgramOnly } from '../../dist/generated/deserialize/ts_range_parent_no_parens.js' ;
11+ import { getLineColumnFromOffset , getOffsetFromLineColumn , initLines , lines , resetLines } from './location.js' ;
1112
1213import type { Program } from '@oxc-project/types' ;
1314import type { Scope , ScopeManager , Variable } from './scope.ts' ;
14- import type { BufferWithArrays , Comment , LineColumn , Node , NodeOrToken , Token } from './types.ts' ;
15+ import type { BufferWithArrays , Comment , Node , NodeOrToken , Token } from './types.ts' ;
1516
1617const require = createRequire ( import . meta. url ) ;
1718
1819const { max } = Math ;
1920
20- // Pattern for splitting source text into lines
21- const LINE_BREAK_PATTERN = / \r \n | [ \r \n \u2028 \u2029 ] / gu;
22-
2321// Text decoder, for decoding source text from buffer
2422const textDecoder = new TextDecoder ( 'utf-8' , { ignoreBOM : true } ) ;
2523
@@ -31,15 +29,10 @@ let hasBOM = false;
3129
3230// Lazily populated when `SOURCE_CODE.text` or `SOURCE_CODE.ast` is accessed,
3331// or `initAst()` is called before the AST is walked.
34- let sourceText : string | null = null ;
32+ export let sourceText : string | null = null ;
3533let sourceByteLen : number = 0 ;
3634export let ast : Program | null = null ;
3735
38- // Lazily populated when `SOURCE_CODE.lines` is accessed.
39- // `lineStartOffsets` starts as `[0]`, and `resetSource` doesn't remove that initial element, so it's never empty.
40- const lines : string [ ] = [ ] ,
41- lineStartOffsets : number [ ] = [ 0 ] ;
42-
4336// Lazily populated when `SOURCE_CODE.visitorKeys` is accessed.
4437let visitorKeys : { [ key : string ] : string [ ] } | null = null ;
4538
@@ -56,7 +49,7 @@ export function setupSourceForFile(bufferInput: BufferWithArrays, hasBOMInput: b
5649/**
5750 * Decode source text from buffer.
5851 */
59- function initSourceText ( ) : void {
52+ export function initSourceText ( ) : void {
6053 const { uint32 } = buffer ,
6154 programPos = uint32 [ DATA_POINTER_POS_32 ] ;
6255 sourceByteLen = uint32 [ ( programPos + SOURCE_LEN_OFFSET ) >> 2 ] ;
@@ -71,35 +64,6 @@ export function initAst(): void {
7164 ast = deserializeProgramOnly ( buffer , sourceText , sourceByteLen ) ;
7265}
7366
74- /**
75- * Split source text into lines.
76- */
77- function initLines ( ) : void {
78- if ( sourceText === null ) initSourceText ( ) ;
79-
80- // This implementation is based on the one in ESLint.
81- // TODO: Investigate if using `String.prototype.matchAll` is faster.
82- // This comment is above ESLint's implementation:
83- /*
84- * Previously, this was implemented using a regex that
85- * matched a sequence of non-linebreak characters followed by a
86- * linebreak, then adding the lengths of the matches. However,
87- * this caused a catastrophic backtracking issue when the end
88- * of a file contained a large number of non-newline characters.
89- * To avoid this, the current implementation just matches newlines
90- * and uses match.index to get the correct line start indices.
91- */
92-
93- // Note: `lineStartOffsets` starts as `[0]`
94- let lastOffset = 0 , offset , match ;
95- while ( ( match = LINE_BREAK_PATTERN . exec ( sourceText ) ) ) {
96- offset = match . index ;
97- lines . push ( sourceText . slice ( lastOffset , offset ) ) ;
98- lineStartOffsets . push ( lastOffset = offset + match [ 0 ] . length ) ;
99- }
100- lines . push ( sourceText . slice ( lastOffset ) ) ;
101- }
102-
10367/**
10468 * Reset source after file has been linted, to free memory.
10569 *
@@ -114,8 +78,7 @@ export function resetSource(): void {
11478 buffer = null ;
11579 sourceText = null ;
11680 ast = null ;
117- lines . length = 0 ;
118- lineStartOffsets . length = 1 ;
81+ resetLines ( ) ;
11982}
12083
12184// `SourceCode` object.
@@ -495,8 +458,8 @@ export const SOURCE_CODE = Object.freeze({
495458 throw new Error ( '`sourceCode.getNodeByRangeIndex` not implemented yet' ) ; // TODO
496459 } ,
497460
498- getLocFromIndex,
499- getIndexFromLoc,
461+ getLocFromIndex : getLineColumnFromOffset ,
462+ getIndexFromLoc : getOffsetFromLineColumn ,
500463
501464 /**
502465 * Check whether any comments exist or not between the given 2 nodes.
@@ -546,97 +509,6 @@ export const SOURCE_CODE = Object.freeze({
546509
547510export type SourceCode = typeof SOURCE_CODE ;
548511
549- /**
550- * Convert a source text index into a (line, column) pair.
551- * @param offset The index of a character in a file.
552- * @returns `{line, column}` location object with 1-indexed line and 0-indexed column.
553- * @throws {TypeError|RangeError } If non-numeric `index`, or `index` out of range.
554- */
555- function getLocFromIndex ( offset : number ) : LineColumn {
556- if ( typeof offset !== 'number' || offset < 0 || ( offset | 0 ) !== offset ) {
557- throw new TypeError ( 'Expected `offset` to be a non-negative integer.' ) ;
558- }
559-
560- // Build `lines` and `lineStartOffsets` tables if they haven't been already.
561- // This also decodes `sourceText` if it wasn't already.
562- if ( lines . length === 0 ) initLines ( ) ;
563-
564- if ( offset > sourceText . length ) {
565- throw new RangeError (
566- `Index out of range (requested index ${ offset } , but source text has length ${ sourceText . length } ).` ,
567- ) ;
568- }
569-
570- // Binary search `lineStartOffsets` for the line containing `offset`
571- let low = 0 , high = lineStartOffsets . length , mid : number ;
572- do {
573- mid = ( ( low + high ) / 2 ) | 0 ; // Use bitwise OR to floor the division
574- if ( offset < lineStartOffsets [ mid ] ) {
575- high = mid ;
576- } else {
577- low = mid + 1 ;
578- }
579- } while ( low < high ) ;
580-
581- return { line : low , column : offset - lineStartOffsets [ low - 1 ] } ;
582- }
583-
584- /**
585- * Convert a `{ line, column }` pair into a range index.
586- * @param loc - A line/column location.
587- * @returns The range index of the location in the file.
588- * @throws {TypeError|RangeError } If `loc` is not an object with a numeric `line` and `column`,
589- * or if the `line` is less than or equal to zero, or the line or column is out of the expected range.
590- */
591- export function getIndexFromLoc ( loc : LineColumn ) : number {
592- if ( loc !== null && typeof loc === 'object' ) {
593- const { line, column } = loc ;
594- if ( typeof line === 'number' && typeof column === 'number' && ( line | 0 ) === line && ( column | 0 ) === column ) {
595- // Build `lines` and `lineStartOffsets` tables if they haven't been already.
596- // This also decodes `sourceText` if it wasn't already.
597- if ( lines . length === 0 ) initLines ( ) ;
598-
599- const linesCount = lineStartOffsets . length ;
600- if ( line <= 0 || line > linesCount ) {
601- throw new RangeError (
602- `Line number out of range (line ${ line } requested). ` +
603- `Line numbers should be 1-based, and less than or equal to number of lines in file (${ linesCount } ).` ,
604- ) ;
605- }
606- if ( column < 0 ) throw new RangeError ( `Invalid column number (column ${ column } requested).` ) ;
607-
608- const lineOffset = lineStartOffsets [ line - 1 ] ;
609- const offset = lineOffset + column ;
610-
611- // Comment from ESLint implementation:
612- /*
613- * By design, `getIndexFromLoc({ line: lineNum, column: 0 })` should return the start index of
614- * the given line, provided that the line number is valid element of `lines`. Since the
615- * last element of `lines` is an empty string for files with trailing newlines, add a
616- * special case where getting the index for the first location after the end of the file
617- * will return the length of the file, rather than throwing an error. This allows rules to
618- * use `getIndexFromLoc` consistently without worrying about edge cases at the end of a file.
619- */
620-
621- let nextLineOffset ;
622- if ( line === linesCount ) {
623- nextLineOffset = sourceText . length ;
624- if ( offset <= nextLineOffset ) return offset ;
625- } else {
626- nextLineOffset = lineStartOffsets [ line ] ;
627- if ( offset < nextLineOffset ) return offset ;
628- }
629-
630- throw new RangeError (
631- `Column number out of range (column ${ column } requested, ` +
632- `but the length of line ${ line } is ${ nextLineOffset - lineOffset } ).` ,
633- ) ;
634- }
635- }
636-
637- throw new TypeError ( 'Expected `loc` to be an object with integer `line` and `column` properties.' ) ;
638- }
639-
640512/**
641513 * Get all the ancestors of a given node.
642514 * @param node - AST node
0 commit comments