You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@annotator.apache.org by ge...@apache.org on 2020/11/20 15:45:10 UTC

[incubator-annotator] 01/03: Linting

This is an automated email from the ASF dual-hosted git repository.

gerben pushed a commit to branch import-dom-seek
in repository https://gitbox.apache.org/repos/asf/incubator-annotator.git

commit d9ed3010cc923f993d854ceba17f535c70176e81
Author: Gerben <ge...@treora.com>
AuthorDate: Fri Nov 20 15:16:12 2020 +0100

    Linting
---
 .eslintrc.js                                     |   2 +
 packages/dom/src/chunker.ts                      |  79 +++++++++--------
 packages/dom/src/code-point-seeker.ts            |  68 ++++++++++-----
 packages/dom/src/normalize-range.ts              |  57 +++++++------
 packages/dom/src/range/cartesian.ts              |   2 +-
 packages/dom/src/seek.ts                         | 103 +++++++++++++++--------
 packages/dom/src/text-position/describe.ts       |   5 +-
 packages/dom/src/text-position/match.ts          |  15 ++--
 packages/dom/src/text-quote/describe.ts          |  41 ++++++---
 packages/dom/src/text-quote/match.ts             |  68 ++++++++++-----
 packages/dom/test/text-position/describe.test.ts |   5 +-
 packages/dom/test/text-position/match-cases.ts   |  17 ++--
 packages/dom/test/text-position/match.test.ts    |   1 -
 packages/dom/test/text-quote/match-cases.ts      |  77 +++++++++--------
 packages/dom/test/text-quote/match.test.ts       |   2 +-
 packages/selector/src/index.ts                   |   7 +-
 16 files changed, 337 insertions(+), 212 deletions(-)

diff --git a/.eslintrc.js b/.eslintrc.js
index 165813d..598d4ab 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -55,6 +55,7 @@ module.exports = {
       },
     ],
     'import/unambiguous': 'error',
+    'no-constant-condition': 'off',
     'prettier/prettier': [
       'error',
       {
@@ -111,6 +112,7 @@ module.exports = {
       plugins: ['@typescript-eslint'],
       rules: {
         '@typescript-eslint/consistent-type-imports': 'error',
+        '@typescript-eslint/no-explicit-any': 'off',
         '@typescript-eslint/no-unused-vars': [
           'error',
           { argsIgnorePattern: '^_' },
diff --git a/packages/dom/src/chunker.ts b/packages/dom/src/chunker.ts
index 8c28f78..8c56924 100644
--- a/packages/dom/src/chunker.ts
+++ b/packages/dom/src/chunker.ts
@@ -18,8 +18,8 @@
  * under the License.
  */
 
-import { normalizeRange } from "./normalize-range";
-import { ownerDocument } from "./owner-document";
+import { normalizeRange } from './normalize-range';
+import { ownerDocument } from './owner-document';
 
 // A Chunk represents a fragment (typically a string) of some document.
 // Subclasses can add further attributes to map the chunk to its position in the
@@ -40,12 +40,15 @@ export function chunkEquals(chunk1: Chunk<any>, chunk2: Chunk<any>): boolean {
   return chunk1.equals ? chunk1.equals(chunk2) : chunk1 === chunk2;
 }
 
-export function chunkRangeEquals(range1: ChunkRange<any>, range2: ChunkRange<any>) {
+export function chunkRangeEquals(
+  range1: ChunkRange<any>,
+  range2: ChunkRange<any>,
+): boolean {
   return (
-    chunkEquals(range1.startChunk, range2.startChunk)
-    && chunkEquals(range1.endChunk, range2.endChunk)
-    && range1.startIndex === range2.startIndex
-    && range1.endIndex === range2.endIndex
+    chunkEquals(range1.startChunk, range2.startChunk) &&
+    chunkEquals(range1.endChunk, range2.endChunk) &&
+    range1.startIndex === range2.startIndex &&
+    range1.endIndex === range2.endIndex
   );
 }
 
@@ -80,11 +83,19 @@ export class EmptyScopeError extends TypeError {
   }
 }
 
-export class TextNodeChunker implements Chunker<PartialTextNode> {
+export class OutOfScopeError extends TypeError {
+  constructor(message?: string) {
+    super(
+      message ||
+        'Cannot convert node to chunk, as it falls outside of chunker’s scope.',
+    );
+  }
+}
 
+export class TextNodeChunker implements Chunker<PartialTextNode> {
   private iter: NodeIterator;
 
-  get currentChunk() {
+  get currentChunk(): PartialTextNode {
     const node = this.iter.referenceNode;
 
     // This test should not actually be needed, but it keeps TypeScript happy.
@@ -94,10 +105,13 @@ export class TextNodeChunker implements Chunker<PartialTextNode> {
   }
 
   nodeToChunk(node: Text): PartialTextNode {
-    if (!this.scope.intersectsNode(node))
-      throw new Error('Cannot convert node to chunk, as it falls outside of chunker’s scope.');
-    const startOffset = (node === this.scope.startContainer) ? this.scope.startOffset : 0;
-    const endOffset = (node === this.scope.endContainer) ? this.scope.endOffset : node.length;
+    if (!this.scope.intersectsNode(node)) throw new OutOfScopeError();
+
+    const startOffset =
+      node === this.scope.startContainer ? this.scope.startOffset : 0;
+    const endOffset =
+      node === this.scope.endContainer ? this.scope.endOffset : node.length;
+
     return {
       node,
       startOffset,
@@ -105,12 +119,12 @@ export class TextNodeChunker implements Chunker<PartialTextNode> {
       data: node.data.substring(startOffset, endOffset),
       equals(other) {
         return (
-          other.node === this.node
-          && other.startOffset === this.startOffset
-          && other.endOffset === this.endOffset
+          other.node === this.node &&
+          other.startOffset === this.startOffset &&
+          other.endOffset === this.endOffset
         );
       },
-    }
+    };
   }
 
   rangeToChunkRange(range: Range): ChunkRange<PartialTextNode> {
@@ -173,28 +187,27 @@ export class TextNodeChunker implements Chunker<PartialTextNode> {
     }
   }
 
-  nextChunk() {
+  nextChunk(): PartialTextNode | null {
     // Move the iterator to after the current node, so nextNode() will cause a jump.
-    if (this.iter.pointerBeforeReferenceNode)
-      this.iter.nextNode();
-    if (this.iter.nextNode())
-      return this.currentChunk;
-    else
-      return null;
+    if (this.iter.pointerBeforeReferenceNode) this.iter.nextNode();
+
+    if (this.iter.nextNode()) return this.currentChunk;
+    else return null;
   }
 
-  previousChunk() {
-    if (!this.iter.pointerBeforeReferenceNode)
-      this.iter.previousNode();
-    if (this.iter.previousNode())
-      return this.currentChunk;
-    else
-      return null;
+  previousChunk(): PartialTextNode | null {
+    if (!this.iter.pointerBeforeReferenceNode) this.iter.previousNode();
+
+    if (this.iter.previousNode()) return this.currentChunk;
+    else return null;
   }
 
-  precedesCurrentChunk(chunk: PartialTextNode) {
+  precedesCurrentChunk(chunk: PartialTextNode): boolean {
     if (this.currentChunk === null) return false;
-    return !!(this.currentChunk.node.compareDocumentPosition(chunk.node) & Node.DOCUMENT_POSITION_PRECEDING);
+    return !!(
+      this.currentChunk.node.compareDocumentPosition(chunk.node) &
+      Node.DOCUMENT_POSITION_PRECEDING
+    );
   }
 }
 
diff --git a/packages/dom/src/code-point-seeker.ts b/packages/dom/src/code-point-seeker.ts
index b97089e..40f19b9 100644
--- a/packages/dom/src/code-point-seeker.ts
+++ b/packages/dom/src/code-point-seeker.ts
@@ -18,49 +18,58 @@
  * under the License.
  */
 
-import { ChunkSeeker } from "./seek";
-import { Chunk } from "./chunker";
+import type { Chunk } from './chunker';
+import type { ChunkSeeker } from './seek';
 
-export class CodePointSeeker<TChunk extends Chunk<string>> implements ChunkSeeker<TChunk, string[]> {
+export class CodePointSeeker<TChunk extends Chunk<string>>
+  implements ChunkSeeker<TChunk, string[]> {
   position = 0;
 
   constructor(public readonly raw: ChunkSeeker<TChunk>) {}
 
-  seekBy(length: number) {
+  seekBy(length: number): void {
     this.seekTo(this.position + length);
   }
 
-  seekTo(target: number) {
+  seekTo(target: number): void {
     this._readOrSeekTo(false, target);
   }
 
-  read(length: number, roundUp?: boolean) {
+  read(length: number, roundUp?: boolean): string[] {
     return this.readTo(this.position + length, roundUp);
   }
 
-  readTo(target: number, roundUp?: boolean) {
+  readTo(target: number, roundUp?: boolean): string[] {
     return this._readOrSeekTo(true, target, roundUp);
   }
 
-  get currentChunk() {
+  get currentChunk(): TChunk {
     return this.raw.currentChunk;
   }
 
-  get offsetInChunk() {
+  get offsetInChunk(): number {
     return this.raw.offsetInChunk;
   }
 
-  seekToChunk(target: TChunk, offset: number = 0) {
+  seekToChunk(target: TChunk, offset = 0): void {
     this._readOrSeekToChunk(false, target, offset);
   }
 
-  readToChunk(target: TChunk, offset: number = 0) {
+  readToChunk(target: TChunk, offset = 0): string[] {
     return this._readOrSeekToChunk(true, target, offset);
   }
 
-  private _readOrSeekToChunk(read: true, target: TChunk, offset?: number): string[]
-  private _readOrSeekToChunk(read: false, target: TChunk, offset?: number): void
-  private _readOrSeekToChunk(read: boolean, target: TChunk, offset: number = 0) {
+  private _readOrSeekToChunk(
+    read: true,
+    target: TChunk,
+    offset?: number,
+  ): string[];
+  private _readOrSeekToChunk(
+    read: false,
+    target: TChunk,
+    offset?: number,
+  ): void;
+  private _readOrSeekToChunk(read: boolean, target: TChunk, offset = 0) {
     const oldRawPosition = this.raw.position;
 
     let s = this.raw.readToChunk(target, offset);
@@ -75,7 +84,7 @@ export class CodePointSeeker<TChunk extends Chunk<string>> implements ChunkSeeke
       s = s.slice(1);
     }
 
-    let result = [...s];
+    const result = [...s];
 
     this.position = movedForward
       ? this.position + result.length
@@ -84,9 +93,17 @@ export class CodePointSeeker<TChunk extends Chunk<string>> implements ChunkSeeke
     if (read) return result;
   }
 
-  private _readOrSeekTo(read: true, target: number, roundUp?: boolean): string[];
+  private _readOrSeekTo(
+    read: true,
+    target: number,
+    roundUp?: boolean,
+  ): string[];
   private _readOrSeekTo(read: false, target: number, roundUp?: boolean): void;
-  private _readOrSeekTo(read: boolean, target: number, roundUp: boolean = false): string[] | void {
+  private _readOrSeekTo(
+    read: boolean,
+    target: number,
+    roundUp = false,
+  ): string[] | void {
     let result: string[] = [];
 
     if (this.position < target) {
@@ -96,7 +113,7 @@ export class CodePointSeeker<TChunk extends Chunk<string>> implements ChunkSeeke
         let s = unpairedSurrogate + this.raw.read(1, true);
         if (endsWithinCharacter(s)) {
           unpairedSurrogate = s.slice(-1); // consider this half-character part of the next string.
-          s = s.slice(0,-1);
+          s = s.slice(0, -1);
         } else {
           unpairedSurrogate = '';
         }
@@ -107,11 +124,14 @@ export class CodePointSeeker<TChunk extends Chunk<string>> implements ChunkSeeke
       if (unpairedSurrogate) this.raw.seekBy(-1); // align with the last complete character.
       if (!roundUp && this.position > target) {
         const overshootInCodePoints = this.position - target;
-        const overshootInCodeUnits = characters.slice(-overshootInCodePoints).join('').length;
+        const overshootInCodeUnits = characters
+          .slice(-overshootInCodePoints)
+          .join('').length;
         this.position -= overshootInCodePoints;
         this.raw.seekBy(-overshootInCodeUnits);
       }
-    } else { // Nearly equal to the if-block, but moving backward in the text.
+    } else {
+      // Nearly equal to the if-block, but moving backward in the text.
       let unpairedSurrogate = '';
       let characters: string[] = [];
       while (this.position > target) {
@@ -129,7 +149,9 @@ export class CodePointSeeker<TChunk extends Chunk<string>> implements ChunkSeeke
       if (unpairedSurrogate) this.raw.seekBy(1);
       if (!roundUp && this.position < target) {
         const overshootInCodePoints = target - this.position;
-        const overshootInCodeUnits = characters.slice(0, overshootInCodePoints).join('').length;
+        const overshootInCodeUnits = characters
+          .slice(0, overshootInCodePoints)
+          .join('').length;
         this.position += overshootInCodePoints;
         this.raw.seekBy(overshootInCodeUnits);
       }
@@ -141,10 +163,10 @@ export class CodePointSeeker<TChunk extends Chunk<string>> implements ChunkSeeke
 
 function endsWithinCharacter(s: string) {
   const codeUnit = s.charCodeAt(s.length - 1);
-  return (0xD800 <= codeUnit && codeUnit <= 0xDBFF)
+  return 0xd800 <= codeUnit && codeUnit <= 0xdbff;
 }
 
 function startsWithinCharacter(s: string) {
   const codeUnit = s.charCodeAt(0);
-  return (0xDC00 <= codeUnit && codeUnit <= 0xDFFF)
+  return 0xdc00 <= codeUnit && codeUnit <= 0xdfff;
 }
diff --git a/packages/dom/src/normalize-range.ts b/packages/dom/src/normalize-range.ts
index a4a758e..30c1e37 100644
--- a/packages/dom/src/normalize-range.ts
+++ b/packages/dom/src/normalize-range.ts
@@ -18,7 +18,7 @@
  * under the License.
  */
 
-import { ownerDocument } from "./owner-document";
+import { ownerDocument } from './owner-document';
 
 // TextRange is a Range that guarantees to always have Text nodes as its start
 // and end nodes. To ensure the type remains correct, it also restricts usage
@@ -57,19 +57,18 @@ export interface TextRange extends Range {
 // after). If the document does not contain any text nodes, an error is thrown.
 export function normalizeRange(range: Range, scope?: Range): TextRange {
   const document = ownerDocument(range);
-  const walker = document.createTreeWalker(
-    document,
-    NodeFilter.SHOW_TEXT,
-    {
-      acceptNode(node: Text) {
-        return (!scope || scope.intersectsNode(node))
-          ? NodeFilter.FILTER_ACCEPT
-          : NodeFilter.FILTER_REJECT;
-      },
+  const walker = document.createTreeWalker(document, NodeFilter.SHOW_TEXT, {
+    acceptNode(node: Text) {
+      return !scope || scope.intersectsNode(node)
+        ? NodeFilter.FILTER_ACCEPT
+        : NodeFilter.FILTER_REJECT;
     },
-  );
+  });
 
-  let [ startContainer, startOffset ] = snapBoundaryPointToTextNode(range.startContainer, range.startOffset);
+  let [startContainer, startOffset] = snapBoundaryPointToTextNode(
+    range.startContainer,
+    range.startOffset,
+  );
 
   // If we point at the end of a text node, move to the start of the next one.
   // The step is repeated to skip over empty text nodes.
@@ -82,7 +81,10 @@ export function normalizeRange(range: Range, scope?: Range): TextRange {
   // Set the range’s start; note this might move its end too.
   range.setStart(startContainer, startOffset);
 
-  let [ endContainer, endOffset ] = snapBoundaryPointToTextNode(range.endContainer, range.endOffset);
+  let [endContainer, endOffset] = snapBoundaryPointToTextNode(
+    range.endContainer,
+    range.endOffset,
+  );
 
   // If we point at the start of a text node, move to the end of the previous one.
   // The step is repeated to skip over empty text nodes.
@@ -103,9 +105,11 @@ export function normalizeRange(range: Range, scope?: Range): TextRange {
 // - otherwise the first boundary point after it whose node is a text node, if any;
 // - otherwise, the last boundary point before it whose node is a text node.
 // If the document has no text nodes, it throws an error.
-function snapBoundaryPointToTextNode(node: Node, offset: number): [Text, number] {
-  if (isText(node))
-    return [node, offset];
+function snapBoundaryPointToTextNode(
+  node: Node,
+  offset: number,
+): [Text, number] {
+  if (isText(node)) return [node, offset];
 
   // Find the node at or right after the boundary point.
   let curNode: Node;
@@ -116,26 +120,27 @@ function snapBoundaryPointToTextNode(node: Node, offset: number): [Text, number]
   } else {
     curNode = node;
     while (curNode.nextSibling === null) {
-      if (curNode.parentNode === null) // Boundary point is at end of document
+      if (curNode.parentNode === null)
+        // Boundary point is at end of document
         throw new Error('not implemented'); // TODO
       curNode = curNode.parentNode;
     }
     curNode = curNode.nextSibling;
   }
 
-  if (isText(curNode))
-    return [curNode, 0];
+  if (isText(curNode)) return [curNode, 0];
 
   // Walk to the next text node, or the last if there is none.
-  const document = node.ownerDocument ?? node as Document;
+  const document = node.ownerDocument ?? (node as Document);
   const walker = document.createTreeWalker(document, NodeFilter.SHOW_TEXT);
   walker.currentNode = curNode;
-  if (walker.nextNode() !== null)
+  if (walker.nextNode() !== null) {
     return [walker.currentNode as Text, 0];
-  else if (walker.previousNode() !== null)
+  } else if (walker.previousNode() !== null) {
     return [walker.currentNode as Text, (walker.currentNode as Text).length];
-  else
+  } else {
     throw new Error('Document contains no text nodes.');
+  }
 }
 
 function isText(node: Node): node is Text {
@@ -144,8 +149,8 @@ function isText(node: Node): node is Text {
 
 function isCharacterData(node: Node): node is CharacterData {
   return (
-    node.nodeType === Node.PROCESSING_INSTRUCTION_NODE
-    || node.nodeType === Node.COMMENT_NODE
-    || node.nodeType === Node.TEXT_NODE
+    node.nodeType === Node.PROCESSING_INSTRUCTION_NODE ||
+    node.nodeType === Node.COMMENT_NODE ||
+    node.nodeType === Node.TEXT_NODE
   );
 }
diff --git a/packages/dom/src/range/cartesian.ts b/packages/dom/src/range/cartesian.ts
index 37e9876..060a27b 100644
--- a/packages/dom/src/range/cartesian.ts
+++ b/packages/dom/src/range/cartesian.ts
@@ -76,7 +76,7 @@ export async function* cartesian<T>(
 
     // Synchronously compute and yield tuples of the partial product.
     yield* scratch.reduce(
-      (a, b) => a.flatMap((v) => b.map((w) => [...v, w])),
+      (acc, next) => acc.flatMap((v) => next.map((w) => [...v, w])),
       [[]] as T[][],
     );
   }
diff --git a/packages/dom/src/seek.ts b/packages/dom/src/seek.ts
index a1f52c8..b27848f 100644
--- a/packages/dom/src/seek.ts
+++ b/packages/dom/src/seek.ts
@@ -18,7 +18,8 @@
  * under the License.
  */
 
-import { Chunk, Chunker, chunkEquals } from "./chunker";
+import type { Chunk, Chunker } from './chunker';
+import { chunkEquals } from './chunker';
 
 const E_END = 'Iterator exhausted before seek ended.';
 
@@ -30,16 +31,20 @@ export interface Seeker<T extends Iterable<any> = string> {
   seekTo(target: number): void;
 }
 
-export interface ChunkSeeker<TChunk extends Chunk<any>, T extends Iterable<any> = string> extends Seeker<T> {
+export interface ChunkSeeker<
+  TChunk extends Chunk<any>,
+  T extends Iterable<any> = string
+> extends Seeker<T> {
   readonly currentChunk: TChunk;
   readonly offsetInChunk: number;
   seekToChunk(chunk: TChunk, offset?: number): void;
   readToChunk(chunk: TChunk, offset?: number): T;
 }
 
-export class TextSeeker<TChunk extends Chunk<string>> implements ChunkSeeker<TChunk> {
+export class TextSeeker<TChunk extends Chunk<string>>
+  implements ChunkSeeker<TChunk> {
   // The chunk containing our current text position.
-  get currentChunk() {
+  get currentChunk(): TChunk {
     return this.chunker.currentChunk;
   }
 
@@ -50,57 +55,71 @@ export class TextSeeker<TChunk extends Chunk<string>> implements ChunkSeeker<TCh
   offsetInChunk = 0;
 
   // The current text position (measured in code units)
-  get position() { return this.currentChunkPosition + this.offsetInChunk; }
+  get position(): number {
+    return this.currentChunkPosition + this.offsetInChunk;
+  }
 
   constructor(protected chunker: Chunker<TChunk>) {
     // Walk to the start of the first non-empty chunk inside the scope.
     this.seekTo(0);
   }
 
-  read(length: number, roundUp: boolean = false) {
+  read(length: number, roundUp = false): string {
     return this.readTo(this.position + length, roundUp);
   }
 
-  readTo(target: number, roundUp: boolean = false) {
+  readTo(target: number, roundUp = false): string {
     return this._readOrSeekTo(true, target, roundUp);
   }
 
-  seekBy(length: number) {
+  seekBy(length: number): void {
     this.seekTo(this.position + length);
   }
 
-  seekTo(target: number) {
+  seekTo(target: number): void {
     this._readOrSeekTo(false, target);
   }
 
-  seekToChunk(target: TChunk, offset: number = 0) {
+  seekToChunk(target: TChunk, offset = 0): void {
     this._readOrSeekToChunk(false, target, offset);
   }
 
-  readToChunk(target: TChunk, offset: number = 0): string {
+  readToChunk(target: TChunk, offset = 0): string {
     return this._readOrSeekToChunk(true, target, offset);
   }
 
-  private _readOrSeekToChunk(read: true, target: TChunk, offset?: number): string
-  private _readOrSeekToChunk(read: false, target: TChunk, offset?: number): void
-  private _readOrSeekToChunk(read: boolean, target: TChunk, offset: number = 0): string | void {
+  private _readOrSeekToChunk(
+    read: true,
+    target: TChunk,
+    offset?: number,
+  ): string;
+  private _readOrSeekToChunk(
+    read: false,
+    target: TChunk,
+    offset?: number,
+  ): void;
+  private _readOrSeekToChunk(
+    read: boolean,
+    target: TChunk,
+    offset = 0,
+  ): string | void {
     const oldPosition = this.position;
     let result = '';
 
     // Walk to the requested chunk.
-    if (!this.chunker.precedesCurrentChunk(target)) { // Search forwards.
+    if (!this.chunker.precedesCurrentChunk(target)) {
+      // Search forwards.
       while (!chunkEquals(this.currentChunk, target)) {
         const [data, nextChunk] = this._readToNextChunk();
         if (read) result += data;
-        if (nextChunk === null)
-          throw new RangeError(E_END);
+        if (nextChunk === null) throw new RangeError(E_END);
       }
-    } else { // Search backwards.
+    } else {
+      // Search backwards.
       while (!chunkEquals(this.currentChunk, target)) {
         const [data, previousChunk] = this._readToPreviousChunk();
         if (read) result = data + result;
-        if (previousChunk === null)
-          throw new RangeError(E_END);
+        if (previousChunk === null) throw new RangeError(E_END);
       }
     }
 
@@ -114,8 +133,7 @@ export class TextSeeker<TChunk extends Chunk<string>> implements ChunkSeeker<TCh
       if (targetPosition >= this.position) {
         // Read further until the target.
         result += this.readTo(targetPosition);
-      }
-      else if (targetPosition >= oldPosition) {
+      } else if (targetPosition >= oldPosition) {
         // We passed by our target position: step back.
         this.seekTo(targetPosition);
         result = result.slice(0, targetPosition - oldPosition);
@@ -128,14 +146,20 @@ export class TextSeeker<TChunk extends Chunk<string>> implements ChunkSeeker<TCh
     }
   }
 
-  private _readOrSeekTo(read: true, target: number, roundUp?: boolean): string
-  private _readOrSeekTo(read: false, target: number, roundUp?: boolean): void
-  private _readOrSeekTo(read: boolean, target: number, roundUp: boolean = false): string | void {
+  private _readOrSeekTo(read: true, target: number, roundUp?: boolean): string;
+  private _readOrSeekTo(read: false, target: number, roundUp?: boolean): void;
+  private _readOrSeekTo(
+    read: boolean,
+    target: number,
+    roundUp = false,
+  ): string | void {
     let result = '';
 
     if (this.position <= target) {
       while (true) {
-        if (this.currentChunkPosition + this.currentChunk.data.length <= target) {
+        const endOfChunk =
+          this.currentChunkPosition + this.currentChunk.data.length;
+        if (endOfChunk <= target) {
           // The target is beyond the current chunk.
           // (we use < not ≤: if the target is *at* the end of the chunk, possibly
           // because the current chunk is empty, we prefer to take the next chunk)
@@ -143,15 +167,19 @@ export class TextSeeker<TChunk extends Chunk<string>> implements ChunkSeeker<TCh
           const [data, nextChunk] = this._readToNextChunk();
           if (read) result += data;
           if (nextChunk === null) {
-            if (this.position === target)
-              break;
-            else
-              throw new RangeError(E_END);
+            if (this.position === target) break;
+            else throw new RangeError(E_END);
           }
         } else {
           // The target is within the current chunk.
-          const newOffset = roundUp ? this.currentChunk.data.length : target - this.currentChunkPosition;
-          if (read) result += this.currentChunk.data.substring(this.offsetInChunk, newOffset);
+          const newOffset = roundUp
+            ? this.currentChunk.data.length
+            : target - this.currentChunkPosition;
+          if (read)
+            result += this.currentChunk.data.substring(
+              this.offsetInChunk,
+              newOffset,
+            );
           this.offsetInChunk = newOffset;
 
           // If we finish end at the end of the chunk, seek to the start of the next non-empty node.
@@ -161,19 +189,22 @@ export class TextSeeker<TChunk extends Chunk<string>> implements ChunkSeeker<TCh
           break;
         }
       }
-    } else { // Similar to the if-block, but moving backward in the text.
+    } else {
+      // Similar to the if-block, but moving backward in the text.
       while (this.position > target) {
         if (this.currentChunkPosition <= target) {
           // The target is within the current chunk.
           const newOffset = roundUp ? 0 : target - this.currentChunkPosition;
-          if (read) result = this.currentChunk.data.substring(newOffset, this.offsetInChunk) + result;
+          if (read)
+            result =
+              this.currentChunk.data.substring(newOffset, this.offsetInChunk) +
+              result;
           this.offsetInChunk = newOffset;
           break;
         } else {
           const [data, previousChunk] = this._readToPreviousChunk();
           if (read) result = data + result;
-          if (previousChunk === null)
-            throw new RangeError(E_END);
+          if (previousChunk === null) throw new RangeError(E_END);
         }
       }
     }
diff --git a/packages/dom/src/text-position/describe.ts b/packages/dom/src/text-position/describe.ts
index d4099a9..5f7f9a3 100644
--- a/packages/dom/src/text-position/describe.ts
+++ b/packages/dom/src/text-position/describe.ts
@@ -19,9 +19,10 @@
  */
 
 import type { TextPositionSelector } from '@annotator/selector';
-import { ownerDocument } from '../owner-document';
-import { Chunk, Chunker, ChunkRange, TextNodeChunker } from '../chunker';
+import type { Chunk, Chunker, ChunkRange } from '../chunker';
+import { TextNodeChunker } from '../chunker';
 import { CodePointSeeker } from '../code-point-seeker';
+import { ownerDocument } from '../owner-document';
 import { TextSeeker } from '../seek';
 
 export async function describeTextPosition(
diff --git a/packages/dom/src/text-position/match.ts b/packages/dom/src/text-position/match.ts
index cc8044e..becd957 100644
--- a/packages/dom/src/text-position/match.ts
+++ b/packages/dom/src/text-position/match.ts
@@ -19,9 +19,10 @@
  */
 
 import type { Matcher, TextPositionSelector } from '@annotator/selector';
-import { TextSeeker } from '../seek';
+import type { Chunk, ChunkRange, Chunker } from '../chunker';
+import { TextNodeChunker } from '../chunker';
 import { CodePointSeeker } from '../code-point-seeker';
-import { Chunk, ChunkRange, TextNodeChunker, Chunker } from '../chunker';
+import { TextSeeker } from '../seek';
 
 export function createTextPositionSelectorMatcher(
   selector: TextPositionSelector,
@@ -41,10 +42,14 @@ export function createTextPositionSelectorMatcher(
 
 export function abstractTextPositionSelectorMatcher(
   selector: TextPositionSelector,
-): <TChunk extends Chunk<any>>(scope: Chunker<TChunk>) => AsyncGenerator<ChunkRange<TChunk>, void, void> {
+): <TChunk extends Chunk<any>>(
+  scope: Chunker<TChunk>,
+) => AsyncGenerator<ChunkRange<TChunk>, void, void> {
   const { start, end } = selector;
 
-  return async function* matchAll<TChunk extends Chunk<string>>(textChunks: Chunker<TChunk>) {
+  return async function* matchAll<TChunk extends Chunk<string>>(
+    textChunks: Chunker<TChunk>,
+  ) {
     const codeUnitSeeker = new TextSeeker(textChunks);
     const codePointSeeker = new CodePointSeeker(codeUnitSeeker);
 
@@ -56,5 +61,5 @@ export function abstractTextPositionSelectorMatcher(
     const endIndex = codeUnitSeeker.offsetInChunk;
 
     yield { startChunk, startIndex, endChunk, endIndex };
-  }
+  };
 }
diff --git a/packages/dom/src/text-quote/describe.ts b/packages/dom/src/text-quote/describe.ts
index 756df1e..3dfa45e 100644
--- a/packages/dom/src/text-quote/describe.ts
+++ b/packages/dom/src/text-quote/describe.ts
@@ -19,10 +19,12 @@
  */
 
 import type { TextQuoteSelector } from '@annotator/selector';
+import type { Chunk, Chunker, ChunkRange } from '../chunker';
+import { TextNodeChunker, chunkRangeEquals } from '../chunker';
 import { ownerDocument } from '../owner-document';
-import { Chunk, Chunker, ChunkRange, TextNodeChunker, chunkRangeEquals } from '../chunker';
+import type { Seeker } from '../seek';
+import { TextSeeker } from '../seek';
 import { abstractTextQuoteSelectorMatcher } from '.';
-import { TextSeeker, Seeker } from '../seek';
 
 export async function describeTextQuote(
   range: Range,
@@ -67,9 +69,11 @@ async function abstractDescribeTextQuote<TChunk extends Chunk<string>>(
       exact,
       prefix,
       suffix,
-    }
+    };
 
-    const matches = abstractTextQuoteSelectorMatcher(tentativeSelector)(scope());
+    const matches = abstractTextQuoteSelectorMatcher(tentativeSelector)(
+      scope(),
+    );
     let nextMatch = await matches.next();
 
     // If this match is the intended one, no need to act.
@@ -95,21 +99,32 @@ async function abstractDescribeTextQuote<TChunk extends Chunk<string>>(
 
     // Count how many characters we’d need as a prefix to disqualify this match.
     seeker1.seekToChunk(target.startChunk, target.startIndex - prefix.length);
-    seeker2.seekToChunk(unintendedMatch.startChunk, unintendedMatch.startIndex - prefix.length);
+    seeker2.seekToChunk(
+      unintendedMatch.startChunk,
+      unintendedMatch.startIndex - prefix.length,
+    );
     const extraPrefix = readUntilDifferent(seeker1, seeker2, true);
 
     // Count how many characters we’d need as a suffix to disqualify this match.
     seeker1.seekToChunk(target.endChunk, target.endIndex + suffix.length);
-    seeker2.seekToChunk(unintendedMatch.endChunk, unintendedMatch.endIndex + suffix.length);
+    seeker2.seekToChunk(
+      unintendedMatch.endChunk,
+      unintendedMatch.endIndex + suffix.length,
+    );
     const extraSuffix = readUntilDifferent(seeker1, seeker2, false);
 
     // Use either the prefix or suffix, whichever is shortest.
-    if (extraPrefix !== undefined && (extraSuffix === undefined || extraPrefix.length <= extraSuffix.length)) {
+    if (
+      extraPrefix !== undefined &&
+      (extraSuffix === undefined || extraPrefix.length <= extraSuffix.length)
+    ) {
       prefix = extraPrefix + prefix;
     } else if (extraSuffix !== undefined) {
       suffix = suffix + extraSuffix;
     } else {
-      throw new Error('Target cannot be disambiguated; how could that have happened‽');
+      throw new Error(
+        'Target cannot be disambiguated; how could that have happened‽',
+      );
     }
   }
 }
@@ -127,18 +142,16 @@ function readUntilDifferent(
     } catch (err) {
       return undefined; // Start/end of text reached: cannot expand result.
     }
-    result = reverse
-      ? nextCharacter + result
-      : result + nextCharacter;
+    result = reverse ? nextCharacter + result : result + nextCharacter;
 
     // Check if the newly added character makes the result differ from the second seeker.
     let comparisonCharacter: string | undefined;
     try {
       comparisonCharacter = seeker2.read(reverse ? -1 : 1);
-    } catch (err) { // A RangeError would merely mean seeker2 is exhausted.
+    } catch (err) {
+      // A RangeError would merely mean seeker2 is exhausted.
       if (!(err instanceof RangeError)) throw err;
     }
-    if (nextCharacter !== comparisonCharacter)
-      return result;
+    if (nextCharacter !== comparisonCharacter) return result;
   }
 }
diff --git a/packages/dom/src/text-quote/match.ts b/packages/dom/src/text-quote/match.ts
index dd69227..9e09990 100644
--- a/packages/dom/src/text-quote/match.ts
+++ b/packages/dom/src/text-quote/match.ts
@@ -19,7 +19,8 @@
  */
 
 import type { Matcher, TextQuoteSelector } from '@annotator/selector';
-import { Chunk, Chunker, ChunkRange, TextNodeChunker, EmptyScopeError } from '../chunker';
+import type { Chunk, Chunker, ChunkRange } from '../chunker';
+import { TextNodeChunker, EmptyScopeError } from '../chunker';
 
 export function createTextQuoteSelectorMatcher(
   selector: TextQuoteSelector,
@@ -31,22 +32,25 @@ export function createTextQuoteSelectorMatcher(
     try {
       textChunks = new TextNodeChunker(scope);
     } catch (err) {
-      if (err instanceof EmptyScopeError)
-        return; // An empty range contains no matches.
-      else
-        throw err;
+      if (err instanceof EmptyScopeError) return;
+      // An empty range contains no matches.
+      else throw err;
     }
 
     for await (const abstractMatch of abstractMatcher(textChunks)) {
       yield textChunks.chunkRangeToRange(abstractMatch);
     }
-  }
+  };
 }
 
 export function abstractTextQuoteSelectorMatcher(
   selector: TextQuoteSelector,
-): <TChunk extends Chunk<any>>(scope: Chunker<TChunk>) => AsyncGenerator<ChunkRange<TChunk>, void, void> {
-  return async function* matchAll<TChunk extends Chunk<string>>(textChunks: Chunker<TChunk>) {
+): <TChunk extends Chunk<any>>(
+  scope: Chunker<TChunk>,
+) => AsyncGenerator<ChunkRange<TChunk>, void, void> {
+  return async function* matchAll<TChunk extends Chunk<string>>(
+    textChunks: Chunker<TChunk>,
+  ) {
     const exact = selector.exact;
     const prefix = selector.prefix || '';
     const suffix = selector.suffix || '';
@@ -78,7 +82,8 @@ export function abstractTextQuoteSelectorMatcher(
 
         // If the current chunk contains the start and/or end of the match, record these.
         if (partialMatch.endChunk === undefined) {
-          const charactersUntilMatchEnd = prefix.length + exact.length - charactersMatched;
+          const charactersUntilMatchEnd =
+            prefix.length + exact.length - charactersMatched;
           if (charactersUntilMatchEnd <= chunkValue.length) {
             partialMatch.endChunk = chunk;
             partialMatch.endIndex = charactersUntilMatchEnd;
@@ -87,20 +92,29 @@ export function abstractTextQuoteSelectorMatcher(
         if (partialMatch.startChunk === undefined) {
           const charactersUntilMatchStart = prefix.length - charactersMatched;
           if (
-            charactersUntilMatchStart < chunkValue.length
-            || partialMatch.endChunk !== undefined // handles an edge case: an empty quote at the end of a chunk.
+            charactersUntilMatchStart < chunkValue.length ||
+            partialMatch.endChunk !== undefined // handles an edge case: an empty quote at the end of a chunk.
           ) {
             partialMatch.startChunk = chunk;
             partialMatch.startIndex = charactersUntilMatchStart;
           }
         }
 
-        const charactersUntilSuffixEnd = searchPattern.length - charactersMatched;
+        const charactersUntilSuffixEnd =
+          searchPattern.length - charactersMatched;
         if (charactersUntilSuffixEnd <= chunkValue.length) {
-          if (chunkValue.startsWith(searchPattern.substring(charactersMatched))) {
+          if (
+            chunkValue.startsWith(searchPattern.substring(charactersMatched))
+          ) {
             yield partialMatch as ChunkRange<TChunk>; // all fields are certainly defined now.
           }
-        } else if (chunkValue === searchPattern.substring(charactersMatched, charactersMatched + chunkValue.length)) {
+        } else if (
+          chunkValue ===
+          searchPattern.substring(
+            charactersMatched,
+            charactersMatched + chunkValue.length,
+          )
+        ) {
           // The chunk is too short to complete the match; comparison has to be completed in subsequent chunks.
           partialMatch.charactersMatched += chunkValue.length;
           remainingPartialMatches.push(partialMatch);
@@ -112,12 +126,19 @@ export function abstractTextQuoteSelectorMatcher(
       if (searchPattern.length <= chunkValue.length) {
         let fromIndex = 0;
         while (fromIndex <= chunkValue.length) {
-          const patternStartIndex = chunkValue.indexOf(searchPattern, fromIndex);
+          const patternStartIndex = chunkValue.indexOf(
+            searchPattern,
+            fromIndex,
+          );
           if (patternStartIndex === -1) break;
           fromIndex = patternStartIndex + 1;
 
           // Handle edge case: an empty searchPattern would already have been yielded at the end of the last chunk.
-          if (patternStartIndex === 0 && searchPattern.length === 0 && !isFirstChunk)
+          if (
+            patternStartIndex === 0 &&
+            searchPattern.length === 0 &&
+            !isFirstChunk
+          )
             continue;
 
           yield {
@@ -131,11 +152,15 @@ export function abstractTextQuoteSelectorMatcher(
 
       // 3. Check if this chunk ends with a partial match (or even multiple partial matches).
       let newPartialMatches: number[] = [];
-      const searchStartPoint = Math.max(chunkValue.length - searchPattern.length + 1, 0);
+      const searchStartPoint = Math.max(
+        chunkValue.length - searchPattern.length + 1,
+        0,
+      );
       for (let i = searchStartPoint; i < chunkValue.length; i++) {
         const character = chunkValue[i];
         newPartialMatches = newPartialMatches.filter(
-          partialMatchStartIndex => (character === searchPattern[i - partialMatchStartIndex])
+          (partialMatchStartIndex) =>
+            character === searchPattern[i - partialMatchStartIndex],
         );
         if (character === searchPattern[0]) newPartialMatches.push(i);
       }
@@ -146,11 +171,12 @@ export function abstractTextQuoteSelectorMatcher(
         };
         if (charactersMatched >= prefix.length + exact.length) {
           partialMatch.endChunk = chunk;
-          partialMatch.endIndex = partialMatchStartIndex + prefix.length + exact.length;
+          partialMatch.endIndex =
+            partialMatchStartIndex + prefix.length + exact.length;
         }
         if (
-          charactersMatched > prefix.length
-          || partialMatch.endChunk !== undefined // handles an edge case: an empty quote at the end of a chunk.
+          charactersMatched > prefix.length ||
+          partialMatch.endChunk !== undefined // handles an edge case: an empty quote at the end of a chunk.
         ) {
           partialMatch.startChunk = chunk;
           partialMatch.startIndex = partialMatchStartIndex + prefix.length;
diff --git a/packages/dom/test/text-position/describe.test.ts b/packages/dom/test/text-position/describe.test.ts
index 2eefd38..9bc9957 100644
--- a/packages/dom/test/text-position/describe.test.ts
+++ b/packages/dom/test/text-position/describe.test.ts
@@ -35,7 +35,10 @@ describe('createTextPositionSelectorMatcher', () => {
         const doc = domParser.parseFromString(html, 'text/html');
         const scope = doc.createRange();
         scope.selectNodeContents(doc);
-        const result = await describeTextPosition(hydrateRange(range, doc), scope);
+        const result = await describeTextPosition(
+          hydrateRange(range, doc),
+          scope,
+        );
         assert.deepEqual(result, selector);
       });
     }
diff --git a/packages/dom/test/text-position/match-cases.ts b/packages/dom/test/text-position/match-cases.ts
index 0916446..6152a1c 100644
--- a/packages/dom/test/text-position/match-cases.ts
+++ b/packages/dom/test/text-position/match-cases.ts
@@ -109,8 +109,7 @@ export const testCases: {
     ],
   },
   'text inside <head>': {
-    html:
-      '<head><title>l😃rem ipsum dolor amet</title></head><b>yada yada</b>',
+    html: '<head><title>l😃rem ipsum dolor amet</title></head><b>yada yada</b>',
     selector: {
       type: 'TextPositionSelector',
       start: 18,
@@ -132,11 +131,13 @@ export const testCases: {
       start: 3,
       end: 3,
     },
-    expected: [{
-      startContainerXPath: '//b/text()',
-      startOffset: 4,
-      endContainerXPath: '//b/text()',
-      endOffset: 4,
-    }],
+    expected: [
+      {
+        startContainerXPath: '//b/text()',
+        startOffset: 4,
+        endContainerXPath: '//b/text()',
+        endOffset: 4,
+      },
+    ],
   },
 };
diff --git a/packages/dom/test/text-position/match.test.ts b/packages/dom/test/text-position/match.test.ts
index 1acaed0..ac9c31f 100644
--- a/packages/dom/test/text-position/match.test.ts
+++ b/packages/dom/test/text-position/match.test.ts
@@ -83,7 +83,6 @@ describe('createTextPositionSelectorMatcher', () => {
     // console.log([...textNode.parentNode.childNodes].map(node => node.textContent))
     // → [ '', 'l😃rem ipsum ', '', 'dolor', '', ' am', '', 'et yada yada', '' ]
 
-
     await testMatcher(doc, scope, selector, [
       {
         startContainerXPath: '//b/text()[4]', // "dolor"
diff --git a/packages/dom/test/text-quote/match-cases.ts b/packages/dom/test/text-quote/match-cases.ts
index 3145b51..5d2866d 100644
--- a/packages/dom/test/text-quote/match-cases.ts
+++ b/packages/dom/test/text-quote/match-cases.ts
@@ -307,45 +307,44 @@ export const testCases: {
       type: 'TextQuoteSelector',
       exact: '',
     },
-    expected:
-      [
-        {
-          startContainerXPath: '//b/text()[1]',
-          startOffset: 0,
-          endContainerXPath: '//b/text()[1]',
-          endOffset: 0,
-        },
-        {
-          startContainerXPath: '//b/text()[1]',
-          startOffset: 1,
-          endContainerXPath: '//b/text()[1]',
-          endOffset: 1,
-        },
-        {
-          startContainerXPath: '//i/text()',
-          startOffset: 1,
-          endContainerXPath: '//i/text()',
-          endOffset: 1,
-        },
-        {
-          startContainerXPath: '//i/text()',
-          startOffset: 2,
-          endContainerXPath: '//i/text()',
-          endOffset: 2,
-        },
-        {
-          startContainerXPath: '//b/text()[2]',
-          startOffset: 1,
-          endContainerXPath: '//b/text()[2]',
-          endOffset: 1,
-        },
-        {
-          startContainerXPath: '//b/text()[2]',
-          startOffset: 2,
-          endContainerXPath: '//b/text()[2]',
-          endOffset: 2,
-        },
-      ],
+    expected: [
+      {
+        startContainerXPath: '//b/text()[1]',
+        startOffset: 0,
+        endContainerXPath: '//b/text()[1]',
+        endOffset: 0,
+      },
+      {
+        startContainerXPath: '//b/text()[1]',
+        startOffset: 1,
+        endContainerXPath: '//b/text()[1]',
+        endOffset: 1,
+      },
+      {
+        startContainerXPath: '//i/text()',
+        startOffset: 1,
+        endContainerXPath: '//i/text()',
+        endOffset: 1,
+      },
+      {
+        startContainerXPath: '//i/text()',
+        startOffset: 2,
+        endContainerXPath: '//i/text()',
+        endOffset: 2,
+      },
+      {
+        startContainerXPath: '//b/text()[2]',
+        startOffset: 1,
+        endContainerXPath: '//b/text()[2]',
+        endOffset: 1,
+      },
+      {
+        startContainerXPath: '//b/text()[2]',
+        startOffset: 2,
+        endContainerXPath: '//b/text()[2]',
+        endOffset: 2,
+      },
+    ],
   },
   'empty quote, with prefix': {
     html: '<b>lorem ipsum dolor amet yada yada</b>',
diff --git a/packages/dom/test/text-quote/match.test.ts b/packages/dom/test/text-quote/match.test.ts
index 8a68cec..97f5c3c 100644
--- a/packages/dom/test/text-quote/match.test.ts
+++ b/packages/dom/test/text-quote/match.test.ts
@@ -194,7 +194,7 @@ async function testMatcher(
   const matcher = createTextQuoteSelectorMatcher(selector);
   const matches = [];
   for await (const value of matcher(scope)) matches.push(value);
-  assert.equal(matches.length, expected.length, "Wrong number of matches.");
+  assert.equal(matches.length, expected.length, 'Wrong number of matches.');
   matches.forEach((match, i) => {
     const expectedRange = expected[i];
     const expectedStartContainer = evaluateXPath(
diff --git a/packages/selector/src/index.ts b/packages/selector/src/index.ts
index ffab70b..73caa05 100644
--- a/packages/selector/src/index.ts
+++ b/packages/selector/src/index.ts
@@ -21,7 +21,12 @@
 import type { Matcher, Selector } from './types';
 
 export type { Matcher, Selector } from './types';
-export type { CssSelector, RangeSelector, TextPositionSelector, TextQuoteSelector } from './types';
+export type {
+  CssSelector,
+  RangeSelector,
+  TextPositionSelector,
+  TextQuoteSelector,
+} from './types';
 
 export function makeRefinable<
   // Any subtype of Selector can be made refinable; but note we limit the value