@@ -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