Use strict typecheck mode & fix issues

Tiny behaviour changes:
- highlightRange now returns a noop function when the range is empty,
  instead of returning undefined.
- passing null as scope to describeTextQuote no longer works (pass
  undefined instead).
diff --git a/packages/dom/src/cartesian.ts b/packages/dom/src/cartesian.ts
index 35424e8..256e313 100644
--- a/packages/dom/src/cartesian.ts
+++ b/packages/dom/src/cartesian.ts
@@ -30,7 +30,16 @@
   // Initialise an empty log for each iterable.
   const logs: T[][] = iterables.map(() => []);
 
-  const nextValuePromises = iterators.map((iterator, iterableNr) =>
+  type NumberedResultPromise = Promise<{
+    nextResult: IteratorResult<T>,
+    iterableNr: number
+  }>;
+
+  function notNull(p: NumberedResultPromise | null): p is NumberedResultPromise {
+    return p !== null
+  }
+
+  const nextValuePromises: Array<NumberedResultPromise | null> = iterators.map((iterator, iterableNr) =>
     iterator
       .next()
       .then(
@@ -41,10 +50,10 @@
   );
 
   // Keep listening as long as any of the iterables is not yet exhausted.
-  while (nextValuePromises.some(p => p !== null)) {
+  while (nextValuePromises.some(notNull)) {
     // Wait until any of the active iterators has produced a new value.
     const { nextResult, iterableNr } = await Promise.race(
-      nextValuePromises.filter(p => p !== null),
+      nextValuePromises.filter(notNull),
     );
 
     // If this iterable was exhausted, stop listening to it and move on.
diff --git a/packages/dom/src/highlight-range.ts b/packages/dom/src/highlight-range.ts
index d46cead..7f76e40 100644
--- a/packages/dom/src/highlight-range.ts
+++ b/packages/dom/src/highlight-range.ts
@@ -33,8 +33,6 @@
   tagName: string = 'mark',
   attributes: Record<string, string> = {}
 ): () => void {
-  if (range.collapsed) return;
-
   // First put all nodes in an array (splits start and end nodes if needed)
   const nodes = textNodesInRange(range);
 
@@ -57,6 +55,9 @@
 
 // Return an array of the text nodes in the range. Split the start and end nodes if required.
 function textNodesInRange(range: Range): Text[] {
+  // If the range is empty, avoid creating and returning an empty text node.
+  if (range.collapsed) return [];
+
   // If the start or end node is a text node and only partly in the range, split it.
   if (
     isTextNode(range.startContainer) &&
@@ -78,6 +79,8 @@
   }
 
   // Collect the text nodes.
+  if (!range.startContainer.ownerDocument)
+    throw new TypeError('range.startContainer lacks an ownerDocument');
   const walker = range.startContainer.ownerDocument.createTreeWalker(
     range.commonAncestorContainer,
     NodeFilter.SHOW_TEXT,
@@ -114,6 +117,7 @@
 
 // Replace [node] with <tagName ...attributes>[node]</tagName>
 function wrapNodeInHighlight(node: Node, tagName: string, attributes: Record<string, string>): HTMLElement {
+  if (!node.ownerDocument) throw new TypeError('Node to be highlighted lacks an ownerDocument');
   const highlightElement = node.ownerDocument.createElement(tagName);
   Object.keys(attributes).forEach(key => {
     highlightElement.setAttribute(key, attributes[key]);
@@ -130,7 +134,7 @@
   if (!highlightElement.parentNode) return;
   if (highlightElement.childNodes.length === 1) {
     highlightElement.parentNode.replaceChild(
-      highlightElement.firstChild,
+      highlightElement.firstChild as ChildNode,
       highlightElement,
     );
   } else {
diff --git a/packages/dom/src/scope.ts b/packages/dom/src/scope.ts
index 2b112ff..24c9b26 100644
--- a/packages/dom/src/scope.ts
+++ b/packages/dom/src/scope.ts
@@ -21,18 +21,22 @@
 import { DomScope } from './types';
 
 export function ownerDocument(scope: DomScope): Document {
+  let document;
   if ('commonAncestorContainer' in scope)
-    return scope.commonAncestorContainer.ownerDocument;
+    document = scope.commonAncestorContainer.ownerDocument;
   else
-    return scope.ownerDocument;
+    document = scope.ownerDocument;
+  if (!document) throw new TypeError('scope lacks an ownerDocument');
+  return document;
 }
 
-export function rangeFromScope(scope: DomScope | null): Range {
+export function rangeFromScope(scope: DomScope): Range {
   if ('commonAncestorContainer' in scope) {
     return scope;
   }
 
   const document = scope.ownerDocument;
+  if (!document) throw new TypeError('scope lacks an ownerDocument');
   const range = document.createRange();
 
   range.selectNodeContents(scope);
diff --git a/packages/dom/src/text-quote/describe.ts b/packages/dom/src/text-quote/describe.ts
index d5168ca..ef7eb53 100644
--- a/packages/dom/src/text-quote/describe.ts
+++ b/packages/dom/src/text-quote/describe.ts
@@ -38,7 +38,7 @@
 
 export async function describeTextQuote(
   range: Range,
-  scope: DomScope = null
+  scope: DomScope = ownerDocument(range).documentElement,
 ): Promise<TextQuoteSelector> {
   const exact = range.toString();
 
@@ -54,9 +54,9 @@
 async function calculateContextForDisambiguation(
   range: Range,
   selector: TextQuoteSelector,
-  scope: DomScope
+  scope: DomScope,
 ): Promise<{ prefix?: string, suffix?: string }> {
-  const scopeAsRange = rangeFromScope(scope || ownerDocument(range).documentElement);
+  const scopeAsRange = rangeFromScope(scope);
   const root = scopeAsRange.commonAncestorContainer;
   const text = scopeAsRange.toString();
 
diff --git a/packages/dom/src/text-quote/match.ts b/packages/dom/src/text-quote/match.ts
index e6e26fc..2657a6f 100644
--- a/packages/dom/src/text-quote/match.ts
+++ b/packages/dom/src/text-quote/match.ts
@@ -62,7 +62,7 @@
       if (!iter.pointerBeforeReferenceNode) {
         // Peek forward and skip over any empty nodes.
         if (iter.nextNode()) {
-          while (iter.referenceNode.nodeValue.length === 0) {
+          while ((iter.referenceNode.nodeValue as String).length === 0) {
             iter.nextNode();
           }
 
@@ -82,7 +82,7 @@
       if (!iter.pointerBeforeReferenceNode) {
         // Peek forward and skip over any empty nodes.
         if (iter.nextNode()) {
-          while (iter.referenceNode.nodeValue.length === 0) {
+          while ((iter.referenceNode.nodeValue as String).length === 0) {
             iter.nextNode();
           }
 
diff --git a/test/data-model.ts b/test/data-model.ts
index 975b361..c52f4e1 100644
--- a/test/data-model.ts
+++ b/test/data-model.ts
@@ -21,7 +21,7 @@
 /* global process */
 
 import fs from 'fs';
-import URL from 'url';
+import { URL } from 'url';
 
 import Ajv from 'ajv';
 import META_SCHEMA from 'ajv/lib/refs/json-schema-draft-04.json';
@@ -76,13 +76,13 @@
       this.skip();
     } else {
       // load the data from the file or URL
-      let url_parsed = URL.parse(url);
-      if (url_parsed.path !== url_parsed.href) {
+      let url_parsed = new URL(url);
+      if (url_parsed.pathname !== url_parsed.href) {
         const data_response = await fetch(url_parsed.href);
         data = await data_response.json();
       } else {
         // assume we have a local file and use that
-        data = JSON.parse(fs.readFileSync(url_parsed.path, 'utf8'));
+        data = JSON.parse(fs.readFileSync(url_parsed.pathname, 'utf8'));
       }
       if (data === '') {
         this.skip();
diff --git a/tsconfig.json b/tsconfig.json
index 8ca98e8..49400f2 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,6 +1,6 @@
 {
     "compilerOptions": {
-        "noImplicitAny": true,
+        "strict": true,
         "noEmit": true,
         "target": "ES2017",
         "lib": [