From 390a92c149abce07b79666f3737705ae7c3dfe8b Mon Sep 17 00:00:00 2001
From: Anthony Fu <anthonyfu117@hotmail.com>
Date: Wed, 17 Jan 2024 17:42:56 +0100
Subject: [PATCH] fix!: rework highlight node (#13)

---
 packages/twoslash-vue/src/index.ts            |  2 +-
 packages/twoslash/src/core.ts                 | 37 ++++---------------
 packages/twoslash/src/legacy.ts               |  7 ++--
 packages/twoslash/src/types/nodes.ts          |  4 +-
 packages/twoslash/src/types/returns.ts        |  2 +-
 packages/twoslash/src/utils.ts                |  1 +
 .../fixtures/throws/invalid-highlights.ts     |  3 --
 packages/twoslash/test/legacy.test.ts         | 10 ++++-
 .../test/results/examples/highlighting.json   |  8 ++--
 .../test/results/tests/inline-highlights.json |  7 +---
 .../results/throws/invalid-highlights.txt     |  6 ---
 11 files changed, 31 insertions(+), 56 deletions(-)
 delete mode 100644 packages/twoslash/test/fixtures/throws/invalid-highlights.ts
 delete mode 100644 packages/twoslash/test/results/throws/invalid-highlights.txt

diff --git a/packages/twoslash-vue/src/index.ts b/packages/twoslash-vue/src/index.ts
index db3b5a4..2ee126c 100644
--- a/packages/twoslash-vue/src/index.ts
+++ b/packages/twoslash-vue/src/index.ts
@@ -85,7 +85,7 @@ export function createTwoslasher(createOptions: CreateTwoslashVueOptions = {}):
       removals: [] as Range[],
       positionCompletions: [] as number[],
       positionQueries: [] as number[],
-      positionHighlights: [] as Range[],
+      positionHighlights: [] as TwoslashReturnMeta['positionHighlights'],
       flagNotations: [] as ParsedFlagNotation[],
     } satisfies Partial<TwoslashReturnMeta>
 
diff --git a/packages/twoslash/src/core.ts b/packages/twoslash/src/core.ts
index 5c488fa..711e66c 100644
--- a/packages/twoslash/src/core.ts
+++ b/packages/twoslash/src/core.ts
@@ -2,7 +2,7 @@ import type { CompilerOptions, CompletionEntry, CompletionTriggerKind, JsxEmit }
 import { createFSBackedSystem, createSystem, createVirtualTypeScriptEnvironment } from '@typescript/vfs'
 import { TwoslashError } from './error'
 import type { CompilerOptionDeclaration, CreateTwoslashOptions, NodeError, NodeWithoutPosition, Position, Range, TwoslashExecuteOptions, TwoslashInstance, TwoslashOptions, TwoslashReturn, TwoslashReturnMeta, VirtualFile } from './types'
-import { areRangesIntersecting, createPositionConverter, findCutNotations, findFlagNotations, findQueryMarkers, getExtension, getIdentifierTextSpans, getObjectHash, isInRange, isInRanges, removeCodeRanges, removeTsExtension, resolveNodePositions, splitFiles, typesToExtension } from './utils'
+import { createPositionConverter, findCutNotations, findFlagNotations, findQueryMarkers, getExtension, getIdentifierTextSpans, getObjectHash, isInRange, isInRanges, removeCodeRanges, removeTsExtension, resolveNodePositions, splitFiles, typesToExtension } from './utils'
 import { validateCodeForErrors } from './validation'
 import { defaultCompilerOptions, defaultHandbookOptions } from './defaults'
 
@@ -239,35 +239,12 @@ export function createTwoslasher(createOptions: CreateTwoslashOptions = {}): Two
 
       // #region get highlights
       for (const highlight of meta.positionHighlights) {
-        if (isInRemoval(highlight[0])) {
-          throw new TwoslashError(
-            `Invalid highlight query`,
-            `The request on line ${pc.indexToPos(highlight[0]).line + 2} for highlight via ^^^ is in a removal range.`,
-            `This is likely that the positioning is off.`,
-          )
-        }
-
-        const file = getFileAtPosition(highlight[0])!
-        const identifiers = getIdentifiersOfFile(file)
-
-        const ids = identifiers.filter(i => areRangesIntersecting(i as unknown as Range, highlight))
-        const matched = ids
-          .map(i => getQuickInfo(file, i[0], i[2]))
-          .filter(Boolean) as NodeWithoutPosition[]
-        if (matched.length) {
-          for (const node of matched) {
-            node.type = 'highlight'
-            nodes.push(node)
-          }
-        }
-        else {
-          const pos = pc.indexToPos(highlight[0])
-          throw new TwoslashError(
-            `Invalid highlight query`,
-            `The request on line ${pos.line + 2} in ${file.filename} for highlight via ^^^ is returned nothing from the compiler.`,
-            `This is likely that the positioning is off.`,
-          )
-        }
+        nodes.push({
+          type: 'highlight',
+          start: highlight[0],
+          length: highlight[1] - highlight[0],
+          text: highlight[2],
+        })
       }
       // #endregion
 
diff --git a/packages/twoslash/src/legacy.ts b/packages/twoslash/src/legacy.ts
index 0a24244..8fd894e 100644
--- a/packages/twoslash/src/legacy.ts
+++ b/packages/twoslash/src/legacy.ts
@@ -136,11 +136,12 @@ export function convertLegacyReturn(result: TwoslashReturn): TwoslashReturnLegac
     highlights: result.highlights
       .map((h): TwoslashReturnLegacy['highlights'][0] => ({
         kind: 'highlight',
-        offset: h.character,
-        start: h.start,
+        // it's a bit confusing that `offset` and `start` are flipped
+        offset: h.start,
+        start: h.character,
         length: h.length,
         line: h.line,
-        text: h.text,
+        text: h.text || '',
       })),
 
     queries: ([
diff --git a/packages/twoslash/src/types/nodes.ts b/packages/twoslash/src/types/nodes.ts
index a2e37df..995bcf4 100644
--- a/packages/twoslash/src/types/nodes.ts
+++ b/packages/twoslash/src/types/nodes.ts
@@ -26,8 +26,10 @@ export interface NodeHover extends NodeBase {
   tags?: [name: string, text: string | undefined][]
 }
 
-export interface NodeHighlight extends Omit<NodeHover, 'type'> {
+export interface NodeHighlight extends NodeBase {
   type: 'highlight'
+  /** The annotation message */
+  text?: string
 }
 
 export interface NodeQuery extends Omit<NodeHover, 'type'> {
diff --git a/packages/twoslash/src/types/returns.ts b/packages/twoslash/src/types/returns.ts
index 6d8664f..a126d5f 100644
--- a/packages/twoslash/src/types/returns.ts
+++ b/packages/twoslash/src/types/returns.ts
@@ -60,7 +60,7 @@ export interface TwoslashReturnMeta {
   /**
    * Positions of errors in the code
    */
-  positionHighlights: Range[]
+  positionHighlights: [start: number, end: number, text?: string][]
 }
 
 export interface ParsedFlagNotation {
diff --git a/packages/twoslash/src/utils.ts b/packages/twoslash/src/utils.ts
index dd1fe15..a3d8840 100644
--- a/packages/twoslash/src/utils.ts
+++ b/packages/twoslash/src/utils.ts
@@ -443,6 +443,7 @@ export function findQueryMarkers(
         meta.positionHighlights.push([
           targetIndex,
           targetIndex + markerLength,
+          match[2]?.trim(),
         ])
       }
     })
diff --git a/packages/twoslash/test/fixtures/throws/invalid-highlights.ts b/packages/twoslash/test/fixtures/throws/invalid-highlights.ts
deleted file mode 100644
index 72f5804..0000000
--- a/packages/twoslash/test/fixtures/throws/invalid-highlights.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-const a = 1;
-//          ^^^
-console.log(a);
diff --git a/packages/twoslash/test/legacy.test.ts b/packages/twoslash/test/legacy.test.ts
index a507b34..30e5620 100644
--- a/packages/twoslash/test/legacy.test.ts
+++ b/packages/twoslash/test/legacy.test.ts
@@ -8,6 +8,8 @@ describe('legacy', async () => {
   await compareCode('./fixtures/examples/cuts-out-unnecessary-code.ts')
   await compareCode('./fixtures/examples/errors-with-generics.ts')
   await compareCode('./fixtures/examples/completions-basic.ts')
+  await compareCode('./fixtures/examples/highlighting.ts')
+  await compareCode('./fixtures/tests/inline-highlights.ts')
 
   async function compareCode(path: string) {
     const code = await fs.readFile(new URL(path, import.meta.url), 'utf-8')
@@ -19,8 +21,8 @@ describe('legacy', async () => {
       function cleanup(t: any) {
         delete t.playgroundURL
 
-        // We have different calculations for queries, that are not trivial to map back
         t.queries.forEach((i: any) => {
+          // We have different calculations for queries, that are not trivial to map back
           delete i.start
           delete i.length
           delete i.text
@@ -28,9 +30,15 @@ describe('legacy', async () => {
         })
 
         t.errors.forEach((i: any) => {
+          // Id has a bit different calculation, as long as it's unique, it should be fine
           delete i.id
         })
 
+        t.highlights.forEach((i: any) => {
+          // It seems the legacy version's line numbers are off for the second highlight nations
+          delete i.line
+        })
+
         return t
       }
 
diff --git a/packages/twoslash/test/results/examples/highlighting.json b/packages/twoslash/test/results/examples/highlighting.json
index 0cdb554..e2fa16e 100644
--- a/packages/twoslash/test/results/examples/highlighting.json
+++ b/packages/twoslash/test/results/examples/highlighting.json
@@ -96,12 +96,10 @@
     },
     {
       "type": "highlight",
-      "text": "var Date: DateConstructor\nnew () => Date (+4 overloads)",
-      "start": 138,
-      "length": 4,
-      "target": "Date",
+      "start": 134,
+      "length": 10,
       "line": 4,
-      "character": 22
+      "character": 18
     },
     {
       "type": "hover",
diff --git a/packages/twoslash/test/results/tests/inline-highlights.json b/packages/twoslash/test/results/tests/inline-highlights.json
index 598c542..47aae16 100644
--- a/packages/twoslash/test/results/tests/inline-highlights.json
+++ b/packages/twoslash/test/results/tests/inline-highlights.json
@@ -3,10 +3,8 @@
   "nodes": [
     {
       "type": "highlight",
-      "text": "type Result = \"pass\" | \"fail\"",
       "start": 5,
       "length": 6,
-      "target": "Result",
       "line": 0,
       "character": 5
     },
@@ -21,10 +19,9 @@
     },
     {
       "type": "highlight",
-      "text": "const hello: \"OK\"",
       "start": 37,
-      "length": 5,
-      "target": "hello",
+      "length": 2,
+      "text": "Sure",
       "line": 2,
       "character": 6
     },
diff --git a/packages/twoslash/test/results/throws/invalid-highlights.txt b/packages/twoslash/test/results/throws/invalid-highlights.txt
deleted file mode 100644
index fa63941..0000000
--- a/packages/twoslash/test/results/throws/invalid-highlights.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-
-## Invalid highlight query
-
-The request on line 2 in index.ts for highlight via ^^^ is returned nothing from the compiler.
-
-This is likely that the positioning is off.
\ No newline at end of file