microsoft/qdk

Public

mirrored fromhttps://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v1.23.0

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/npm/qsharp/src/diagnostics.ts

105lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4import { log } from "./log.js";
5import type { IQSharpError, IRange } from "../lib/web/qsc_wasm.js";
6
7/**
8 * Public error type for most `qsharp-lang` functions.
9 *
10 * This is typically thrown by functions that deal with compiling and
11 * running code. Contains one or more VS Code-like diagnostics.
12 */
13export class QdkDiagnostics extends Error {
14 constructor(public readonly diagnostics: IQSharpError[]) {
15 const message = shortMessage(diagnostics);
16 super(message);
17 this.name = "QdkDiagnostics";
18 }
19}
20
21/**
22 * Wrapper around QDK WASM functions to convert exceptions to a more ergonomic type.
23 *
24 * Many of the WASM functions throw exceptions that are of type `string`,
25 * which are really just JSON-serialized `IQSharpError[]`s.
26 *
27 * This function converts those exceptions into `QdkDiagnostics` instances
28 * that properly inherit from `Error`.
29 */
30export async function callAndTransformExceptions<T>(fn: () => Promise<T>) {
31 try {
32 return await fn();
33 } catch (e: unknown) {
34 const QdkDiagnostics = tryParseQdkDiagnostics(e);
35 if (QdkDiagnostics) {
36 throw QdkDiagnostics;
37 }
38 throw e;
39 }
40}
41
42/**
43 * If the error is a string containing JSON-serialized `IQSharpError[]`,
44 * creates a `QdkDiagnostics` instance from it.
45 */
46function tryParseQdkDiagnostics(e: unknown): QdkDiagnostics | undefined {
47 if (typeof e === "string") {
48 try {
49 const errors = JSON.parse(e);
50 // Check for the shape of IQSharpError[]
51 if (
52 Array.isArray(errors) &&
53 errors.length > 0 &&
54 errors[0].document &&
55 errors[0].diagnostic
56 ) {
57 return new QdkDiagnostics(errors);
58 }
59 } catch {
60 // Couldn't parse the error as JSON.
61 log.warn(`could not parse error string ${e}`);
62 }
63 }
64
65 return undefined;
66}
67
68/**
69 * Constructs a human-readable message from the first error.
70 */
71function shortMessage(errors: IQSharpError[]) {
72 const error = errors[0];
73
74 return `${error.diagnostic.message}${friendlyLocation(
75 error.document,
76 error.diagnostic.range,
77 )}`;
78}
79
80/**
81 * Constructs a human-readable location string for an error.
82 */
83function friendlyLocation(uriOrName: string, range: IRange) {
84 if (uriOrName === "<project>") {
85 return "";
86 }
87 // Don't make any assumptions about the format of uriOrName,
88 // it could be a file path, a URI with an arbitrary scheme, or just a name.
89 // If it contains slashes, we assume it's a path and extract the basename.
90 // Best effort, for display only.
91 const lastSlash = Math.max(
92 uriOrName.lastIndexOf("/"),
93 uriOrName.lastIndexOf("\\"),
94 );
95 const basename =
96 lastSlash >= 0 ? uriOrName.substring(lastSlash + 1) : uriOrName;
97
98 // avoid printing :1:1 since that's the default if the original error didn't specify a range
99 const lineColumn =
100 range.start.line > 0 || range.start.character > 0
101 ? `:${range.start.line + 1}:${range.start.character + 1}`
102 : "";
103
104 return ` at ${basename}${lineColumn}`;
105}
106