Skip to content

Commit e33ad43

Browse files
committed
feat: incremental index support
Signed-off-by: Lexus Drumgold <unicornware@flexdevelopment.llc>
1 parent 5bf2368 commit e33ad43

File tree

11 files changed

+218
-32
lines changed

11 files changed

+218
-32
lines changed

README.md

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,17 @@
2020
- [Use](#use)
2121
- [API](#api)
2222
- [`Location([file][, start])`](#locationfile-start)
23+
- [`Location#indices`](#locationindices)
2324
- [`Location#offset([point])`](#locationoffsetpoint)
25+
- [`Location#place`](#locationplace)
2426
- [`Location#point([offset])`](#locationpointoffset)
27+
- [`Location#start`](#locationstart)
28+
- [`Column`](#column)
29+
- [`Indices`](#indices)
30+
- [`Line`](#line)
31+
- [`Offset`](#offset)
2532
- [`Point`](#point)
33+
- [`SerializedPoint`](#serializedpoint)
2634
- [Types](#types)
2735
- [Contribute](#contribute)
2836

@@ -32,7 +40,7 @@ This is a tiny but useful package that facilitates conversions between [points a
3240

3341
## When should I use this?
3442

35-
This utility is useful when adding [*positional information*][positional information] to [unist][unist] nodes, or when
43+
This utility is useful when adding [*positional information*][positional-information] to [unist][unist] nodes, or when
3644
building packages that require location data, such as a set of lint rules.
3745

3846
## Install
@@ -100,9 +108,19 @@ Create a new location index to translate between point and offset based location
100108

101109
Pass a `start` point to make relative conversions. Any point or offset accessed will be relative to the given point.
102110

111+
An incremental index can be built when `file` is `null` or `undefined`, in which case [`indices`](#locationindices) (and
112+
[`place`](#locationplace)) must be updated manually.
113+
103114
- `file` ([`Value`][vfile-value] | [`VFile`][vfile-api] | `null` | `undefined`) &mdash; file to index
104115
- `start` ([`Point`](#point) | `null` | `undefined`) &mdash; point before first character
105116

117+
#### `Location#indices`
118+
119+
([`Indices`](#indices))
120+
Map, where each key/value pair is either the index of a character in the file ([offset](#offset)) and a [point](#point),
121+
or a [line and column](#serializedpoint) in the file and an offset.
122+
Both the key and value are relative to [`start`](#locationstart).
123+
106124
#### `Location#offset([point])`
107125

108126
Get an offset for `point`.
@@ -115,7 +133,15 @@ Get an offset for `point`.
115133

116134
##### Returns
117135

118-
([`Offset`][offset]) Index of character in file or `-1`.
136+
([`Offset`](#offset)) Index of character in file or `-1`.
137+
138+
#### `Location#place`
139+
140+
([`Point`](#point))
141+
Current point.
142+
143+
> 👉 Useful for building an incremental index. This point is deeply equal to [`start`](#locationstart) when a file is
144+
> auto-indexed and never altered.
119145
120146
#### `Location#point([offset])`
121147

@@ -126,21 +152,67 @@ Get a point for `offset`.
126152
127153
##### Parameters
128154

129-
- `offset` ([`Offset`][offset] | `null` | `undefined`) &mdash; index of character in file
155+
- `offset` ([`Offset`](#offset) | `null` | `undefined`) &mdash; index of character in file
130156

131157
##### Returns
132158

133159
([`Point`](#point)) Place in file.
134160

161+
#### `Location#start`
162+
163+
([`Readonly<Point>`](#point))
164+
Point before first character in file.
165+
166+
### `Column`
167+
168+
Column in a source file (`1`-indexed integer) (TypeScript type).
169+
170+
```ts
171+
type Column = number
172+
```
173+
174+
### `Indices`
175+
176+
Map, where each key/value pair is either the index of a character in a source file ([offset](#offset)) and a
177+
[point](#point), or a [line and column](#serializedpoint) in the source file and an offset (TypeScript type).
178+
179+
```ts
180+
type Indices = { [offset: Offset]: Point; [point: SerializedPoint]: Offset }
181+
```
182+
183+
### `Line`
184+
185+
Line in a source file (`1`-indexed integer) (TypeScript type).
186+
187+
```ts
188+
type Line = number
189+
```
190+
191+
### `Offset`
192+
193+
Index of a character in a source file (`0`-indexed integer) (TypeScript type).
194+
195+
```ts
196+
type Offset = number
197+
```
198+
135199
### `Point`
136200
137201
One place in a source file (TypeScript interface).
138202
139203
#### Properties
140204
141-
- `column` (`number`) &mdash; column in source file (`1`-indexed integer)
142-
- `line` (`number`) &mdash; line in source file (`1`-indexed integer)
143-
- `offset` ([`Offset`][offset]) &mdash; index of character in source file (`0`-indexed integer)
205+
- `column` ([`Column`](#column)) &mdash; column in source file (`1`-indexed integer)
206+
- `line` ([`Line`](#line)) &mdash; line in source file (`1`-indexed integer)
207+
- `offset` ([`Offset`](#offset)) &mdash; index of character in source file (`0`-indexed integer)
208+
209+
### `SerializedPoint`
210+
211+
String representing one place in a source file (TypeScript type).
212+
213+
```ts
214+
type SerializedPoint = `${Line}:${Column}`
215+
```
144216
145217
## Types
146218
@@ -155,9 +227,8 @@ community you agree to abide by its terms.
155227
156228
[esm]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c
157229
[esmsh]: https://esm.sh/
158-
[offset]: https://github.com/flex-development/unist-util-types#offset
159230
[point]: https://github.com/syntax-tree/unist#point
160-
[positional information]: https://github.com/syntax-tree/unist#positional-information
231+
[positional-information]: https://github.com/syntax-tree/unist#positional-information
161232
[typescript]: https://www.typescriptlang.org
162233
[unist]: https://github.com/syntax-tree/unist
163234
[vfile]: https://github.com/vfile/vfile

build.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import tsconfig from './tsconfig.build.json' assert { type: 'json' }
1414
*/
1515
const config: Config = defineBuildConfig({
1616
charset: 'utf8',
17-
entries: [{ dts: false, ignore: ['interfaces'] }, { dts: 'only' }],
17+
entries: [{ dts: false, ignore: ['interfaces', 'types'] }, { dts: 'only' }],
1818
target: ['node18', tsconfig.compilerOptions.target],
1919
tsconfig: 'tsconfig.build.json'
2020
})

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55

66
export type * from './interfaces'
77
export { default as Location } from './location'
8+
export type * from './types'

src/interfaces/__tests__/point.spec-d.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,24 @@
33
* @module vfile-location/interfaces/tests/unit-d/Point
44
*/
55

6-
import type { Offset } from '@flex-development/unist-util-types'
6+
import type { Column, Line, Offset } from '@flex-development/unist-util-types'
77
import type * as unist from 'unist'
88
import type TestSubject from '../point'
99

1010
describe('unit-d:interfaces/Point', () => {
11-
it('should extend unist.Point', () => {
12-
expectTypeOf<TestSubject>().toMatchTypeOf<unist.Point>()
11+
it('should match [column: Column]', () => {
12+
expectTypeOf<TestSubject>().toHaveProperty('column').toEqualTypeOf<Column>()
13+
})
14+
15+
it('should match [line: Line]', () => {
16+
expectTypeOf<TestSubject>().toHaveProperty('line').toEqualTypeOf<Line>()
1317
})
1418

1519
it('should match [offset: Offset]', () => {
1620
expectTypeOf<TestSubject>().toHaveProperty('offset').toEqualTypeOf<Offset>()
1721
})
22+
23+
it('should match unist.Point', () => {
24+
expectTypeOf<TestSubject>().toMatchTypeOf<unist.Point>()
25+
})
1826
})

src/interfaces/point.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,26 @@
33
* @module vfile-location/interfaces/Point
44
*/
55

6-
import type { Offset } from '@flex-development/unist-util-types'
7-
import type * as unist from 'unist'
6+
import type { Column, Line, Offset } from '@flex-development/unist-util-types'
87

98
/**
109
* One place in a source file.
11-
*
12-
* @see {@linkcode unist.Point}
13-
*
14-
* @extends {unist.Point}
1510
*/
16-
interface Point extends unist.Point {
11+
interface Point {
12+
/**
13+
* Column in a source file (`1`-indexed integer).
14+
*
15+
* @see {@linkcode Column}
16+
*/
17+
column: Column
18+
19+
/**
20+
* Line in a source file (`1`-indexed integer).
21+
*
22+
* @see {@linkcode Line}
23+
*/
24+
line: Line
25+
1726
/**
1827
* Index of character in a source file (`0`-indexed integer).
1928
*

src/location.ts

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { Offset } from '@flex-development/unist-util-types'
77
import type * as unist from 'unist'
88
import type { VFile, Value } from 'vfile'
99
import type { Point } from './interfaces'
10+
import type { Indices } from './types'
1011

1112
/**
1213
* Location index.
@@ -18,17 +19,32 @@ import type { Point } from './interfaces'
1819
class Location {
1920
/**
2021
* Map, where each key/value pair is either the index of a character in the
21-
* source file ({@linkcode Offset}) and a {@linkcode Point}, or a line and
22-
* column in the source file and an offset.
22+
* file ({@linkcode Offset}) and a {@linkcode Point}, or a line and column in
23+
* the file and an offset.
2324
*
2425
* Both the key and value are relative to {@linkcode start}.
2526
*
26-
* @private
27-
* @readonly
27+
* @see {@linkcode Indices}
28+
*
29+
* @public
2830
* @instance
29-
* @member {Record<Offset, Readonly<Point>> & Record<string, Offset>}
31+
* @member {Indices}
3032
*/
31-
readonly #indices: Record<Offset, Readonly<Point>> & Record<string, Offset>
33+
public indices: Indices
34+
35+
/**
36+
* Current point.
37+
*
38+
* > 👉 Useful for building an incremental index. This point is deeply equal
39+
* > to {@linkcode start} when a file is auto-indexed and never altered.
40+
*
41+
* @see {@linkcode Point}
42+
*
43+
* @public
44+
* @instance
45+
* @member {Point} place
46+
*/
47+
public place: Point
3248

3349
/**
3450
* Point before first character in file.
@@ -49,6 +65,10 @@ class Location {
4965
* Pass a `start` point to make relative conversions. Any point or offset
5066
* accessed will be relative to the given point.
5167
*
68+
* An incremental index can be built when `file` is `null` or `undefined`, in
69+
* which case {@linkcode indices} (and {@linkcode place}) must be updated
70+
* manually.
71+
*
5272
* @see {@linkcode Point}
5373
* @see {@linkcode VFile}
5474
* @see {@linkcode Value}
@@ -60,9 +80,9 @@ class Location {
6080
file?: Value | VFile | null | undefined,
6181
start?: Point | null | undefined
6282
) {
63-
this.#indices = {}
64-
this.start = Object.assign({}, start ?? { column: 1, line: 1, offset: 0 })
65-
this.start = Object.freeze(this.start)
83+
this.indices = {}
84+
this.place = Object.assign({}, start ?? { column: 1, line: 1, offset: 0 })
85+
this.start = { ...this.place }
6686

6787
// index file
6888
if (file !== null && file !== undefined) {
@@ -74,8 +94,8 @@ class Location {
7494
const point: Point = { ...this.start }
7595

7696
for (const char of String(file) + '\n') {
77-
this.#indices[point.offset] = { ...point }
78-
this.#indices[`${point.line}:${point.column}`] = point.offset
97+
this.indices[point.offset] = { ...point }
98+
this.indices[`${point.line}:${point.column}`] = point.offset
7999

80100
// advance point
81101
if (/[\n\r]/.test(char)) {
@@ -106,7 +126,7 @@ class Location {
106126
* @return {Offset} Index of character in file or `-1`
107127
*/
108128
public offset(point?: unist.Point | null | undefined): Offset {
109-
return this.#indices[`${point?.line}:${point?.column}`] ?? -1
129+
return this.indices[<never>`${point?.line}:${point?.column}`] ?? -1
110130
}
111131

112132
/**
@@ -125,7 +145,7 @@ class Location {
125145
* @return {Point} Place in file
126146
*/
127147
public point(offset?: Offset | null | undefined): Point {
128-
return this.#indices[offset ?? Number.NaN] ?? {
148+
return this.indices[offset ?? Number.NaN] ?? {
129149
column: -1,
130150
line: -1,
131151
offset: offset ?? -1

src/types/__tests__/indices.spec-d.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* @file Type Tests - Indices
3+
* @module vfile-location/types/tests/unit-d/Indices
4+
*/
5+
6+
import type { Point } from '#src/interfaces'
7+
import type { Offset } from '@flex-development/unist-util-types'
8+
import type TestSubject from '../indices'
9+
import type SerializedPoint from '../serialized-point'
10+
11+
describe('unit-d:types/Indices', () => {
12+
it('should match [[offset: Offset]: Point]', () => {
13+
expectTypeOf<TestSubject>()
14+
.toHaveProperty<Offset>(0)
15+
.toEqualTypeOf<Point>()
16+
})
17+
18+
it('should match [[point: SerializedPoint]: Offset]', () => {
19+
expectTypeOf<TestSubject>()
20+
.toHaveProperty<SerializedPoint>('1:1')
21+
.toEqualTypeOf<Offset>()
22+
})
23+
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* @file Type Tests - SerializedPoint
3+
* @module vfile-location/types/tests/unit-d/SerializedPoint
4+
*/
5+
6+
import type { Column, Line } from '@flex-development/unist-util-types'
7+
import type TestSubject from '../serialized-point'
8+
9+
describe('unit-d:types/SerializedPoint', () => {
10+
it('should equal `${Line}:${Column}`', () => {
11+
expectTypeOf<TestSubject>().toEqualTypeOf<`${Line}:${Column}`>()
12+
})
13+
})

src/types/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/**
2+
* @file Entry Point - Type Aliases
3+
* @module vfile-location/types
4+
*/
5+
6+
export type { Column, Line, Offset } from '@flex-development/unist-util-types'
7+
export type { default as Indices } from './indices'
8+
export type { default as SerializedPoint } from './serialized-point'

src/types/indices.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* @file Type Aliases - Indices
3+
* @module vfile-location/types/Indices
4+
*/
5+
6+
import type { Point } from '#src/interfaces'
7+
import type { Offset } from '@flex-development/unist-util-types'
8+
import type SerializedPoint from './serialized-point'
9+
10+
/**
11+
* Map, where each key/value pair is either the index of a character in a
12+
* source file ({@linkcode Offset}) and a {@linkcode Point}, or a line and
13+
* column in the source file and an offset.
14+
*/
15+
type Indices = { [offset: Offset]: Point; [point: SerializedPoint]: Offset }
16+
17+
export type { Indices as default }

0 commit comments

Comments
 (0)