Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/error messages #1327

Merged
merged 8 commits into from
Nov 19, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 22 additions & 9 deletions deno/common/ts_morph_common.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,28 +345,41 @@ export interface TsSourceFileContainer {
/** Decorator for memoizing the result of a method or get accessor. */
export declare function Memoize(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>): void;

/** Minimal attributes to show a error message with source */
export declare type MaybeTraceAbleNode = undefined | null | {} | {
dsherret marked this conversation as resolved.
Show resolved Hide resolved
getSourceFile: () => {
getFilePath: () => StandardizedFilePath;
getText: () => string;
};
getPos(): number;
};

/** Collection of helper functions that can be used to throw errors. */
export declare namespace errors {
/** Base error class. */
abstract class BaseError extends Error {
readonly message: string;
readonly source: {
fileName: string;
pos: ts.LineAndCharacter;
} | undefined;
protected constructor();
}
/** Thrown when there is a problem with a provided argument. */
class ArgumentError extends BaseError {
constructor(argName: string, message: string);
constructor(argName: string, message: string, node?: MaybeTraceAbleNode);
}
/** Thrown when an argument is null or whitespace. */
class ArgumentNullOrWhitespaceError extends ArgumentError {
constructor(argName: string);
constructor(argName: string, node?: MaybeTraceAbleNode);
}
/** Thrown when an argument is out of range. */
class ArgumentOutOfRangeError extends ArgumentError {
constructor(argName: string, value: number, range: [number, number]);
constructor(argName: string, value: number, range: [number, number], node?: MaybeTraceAbleNode);
}
/** Thrown when an argument does not match an expected type. */
class ArgumentTypeError extends ArgumentError {
constructor(argName: string, expectedType: string, actualType: string);
constructor(argName: string, expectedType: string, actualType: string, node?: MaybeTraceAbleNode);
}
/** Thrown when a file or directory path was not found. */
class PathNotFoundError extends BaseError {
Expand All @@ -384,11 +397,11 @@ export declare namespace errors {
}
/** Thrown when an action was taken that is not allowed. */
class InvalidOperationError extends BaseError {
constructor(message: string);
constructor(message: string, node?: MaybeTraceAbleNode);
}
/** Thrown when a certain behaviour or feature has not been implemented. */
class NotImplementedError extends BaseError {
constructor(message?: string);
constructor(message?: string, node?: MaybeTraceAbleNode);
}
/** Thrown when an operation is not supported. */
class NotSupportedError extends BaseError {
Expand Down Expand Up @@ -433,7 +446,7 @@ export declare namespace errors {
* Gets an error saying that a feature is not implemented for a certain syntax kind.
* @param kind - Syntax kind that isn't implemented.
*/
function throwNotImplementedForSyntaxKindError(kind: ts.SyntaxKind): never;
function throwNotImplementedForSyntaxKindError(kind: ts.SyntaxKind, node?: MaybeTraceAbleNode): never;
/**
* Throws an Argument
* @param value
Expand All @@ -445,12 +458,12 @@ export declare namespace errors {
* @param value - Value to check.
* @param errorMessage - Error message to throw when not defined.
*/
function throwIfNullOrUndefined<T>(value: T | undefined, errorMessage: string | (() => string)): T;
function throwIfNullOrUndefined<T>(value: T | undefined, errorMessage: string | (() => string), node?: MaybeTraceAbleNode): T;
/**
* Throw if the value should have been the never type.
* @param value - Value to check.
*/
function throwNotImplementedForNeverValueError(value: never): never;
function throwNotImplementedForNeverValueError(value: never, sourceNode?: MaybeTraceAbleNode): never;
/**
* Throws an error if the actual value does not equal the expected value.
* @param actual - Actual value.
Expand Down
327 changes: 174 additions & 153 deletions deno/common/ts_morph_common.js

Large diffs are not rendered by default.

624 changes: 312 additions & 312 deletions deno/ts_morph.d.ts

Large diffs are not rendered by default.

490 changes: 245 additions & 245 deletions deno/ts_morph.js

Large diffs are not rendered by default.

31 changes: 22 additions & 9 deletions packages/common/lib/ts-morph-common.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,28 +344,41 @@ export interface TsSourceFileContainer {
/** Decorator for memoizing the result of a method or get accessor. */
export declare function Memoize(target: any, propertyName: string, descriptor: TypedPropertyDescriptor<any>): void;

/** Minimal attributes to show a error message with source */
export declare type MaybeTraceAbleNode = undefined | null | {} | {
getSourceFile: () => {
getFilePath: () => StandardizedFilePath;
getText: () => string;
};
getPos(): number;
};

/** Collection of helper functions that can be used to throw errors. */
export declare namespace errors {
/** Base error class. */
abstract class BaseError extends Error {
readonly message: string;
readonly source: {
fileName: string;
pos: ts.LineAndCharacter;
} | undefined;
protected constructor();
}
/** Thrown when there is a problem with a provided argument. */
class ArgumentError extends BaseError {
constructor(argName: string, message: string);
constructor(argName: string, message: string, node?: MaybeTraceAbleNode);
}
/** Thrown when an argument is null or whitespace. */
class ArgumentNullOrWhitespaceError extends ArgumentError {
constructor(argName: string);
constructor(argName: string, node?: MaybeTraceAbleNode);
}
/** Thrown when an argument is out of range. */
class ArgumentOutOfRangeError extends ArgumentError {
constructor(argName: string, value: number, range: [number, number]);
constructor(argName: string, value: number, range: [number, number], node?: MaybeTraceAbleNode);
}
/** Thrown when an argument does not match an expected type. */
class ArgumentTypeError extends ArgumentError {
constructor(argName: string, expectedType: string, actualType: string);
constructor(argName: string, expectedType: string, actualType: string, node?: MaybeTraceAbleNode);
}
/** Thrown when a file or directory path was not found. */
class PathNotFoundError extends BaseError {
Expand All @@ -383,11 +396,11 @@ export declare namespace errors {
}
/** Thrown when an action was taken that is not allowed. */
class InvalidOperationError extends BaseError {
constructor(message: string);
constructor(message: string, node?: MaybeTraceAbleNode);
}
/** Thrown when a certain behaviour or feature has not been implemented. */
class NotImplementedError extends BaseError {
constructor(message?: string);
constructor(message?: string, node?: MaybeTraceAbleNode);
}
/** Thrown when an operation is not supported. */
class NotSupportedError extends BaseError {
Expand Down Expand Up @@ -432,7 +445,7 @@ export declare namespace errors {
* Gets an error saying that a feature is not implemented for a certain syntax kind.
* @param kind - Syntax kind that isn't implemented.
*/
function throwNotImplementedForSyntaxKindError(kind: ts.SyntaxKind): never;
function throwNotImplementedForSyntaxKindError(kind: ts.SyntaxKind, node?: MaybeTraceAbleNode): never;
/**
* Throws an Argument
* @param value
Expand All @@ -444,12 +457,12 @@ export declare namespace errors {
* @param value - Value to check.
* @param errorMessage - Error message to throw when not defined.
*/
function throwIfNullOrUndefined<T>(value: T | undefined, errorMessage: string | (() => string)): T;
function throwIfNullOrUndefined<T>(value: T | undefined, errorMessage: string | (() => string), node?: MaybeTraceAbleNode): T;
/**
* Throw if the value should have been the never type.
* @param value - Value to check.
*/
function throwNotImplementedForNeverValueError(value: never): never;
function throwNotImplementedForNeverValueError(value: never, sourceNode?: MaybeTraceAbleNode): never;
/**
* Throws an error if the actual value does not equal the expected value.
* @param actual - Actual value.
Expand Down
262 changes: 131 additions & 131 deletions packages/common/src/data/libFiles.ts

Large diffs are not rendered by default.

77 changes: 54 additions & 23 deletions packages/common/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,74 @@ import { StandardizedFilePath } from "./fileSystem";
import { getSyntaxKindName } from "./helpers";
import { ts } from "./typescript";

/** Minimal attributes to show a error message with source */
export type MaybeTraceAbleNode = undefined | null | {} | {
// TODO: Find correct type
getSourceFile: () => {
getFilePath: () => StandardizedFilePath;
getText: () => string;
}
getPos(): number;
}

/** Collection of helper functions that can be used to throw errors. */
export namespace errors {
/** Base error class. */
export abstract class BaseError extends Error {
public readonly source: { fileName: string; pos: ts.LineAndCharacter } | undefined;
/** @private */
constructor(public readonly message: string) {
super(message);

this.message = message;
constructor(public readonly message: string, node?: MaybeTraceAbleNode) {
let messageWithSource = message;
let source: { fileName: string; pos: ts.LineAndCharacter } | undefined;
if (node && "getSourceFile" in node && "getPos" in node) {
try {
const sourceFile = node.getSourceFile();
const sourceCode = sourceFile.getText();
const pos = node.getPos();
const textBeforePos = sourceCode.substring(0, pos);
const line = textBeforePos.match(/\n/g)?.length || 0;
const brokenLineStart = textBeforePos.lastIndexOf("\n", pos);
const brokenLineEnd = sourceCode.indexOf("\n", pos);
const brokenLine = sourceCode.substring(brokenLineStart + 1, brokenLineEnd === -1 ? undefined : brokenLineEnd);
source = { fileName: sourceFile.getFilePath(), pos: { line, character: pos - brokenLineStart } };
const linePrefix = `> ${source.pos.line + 1} |`;
messageWithSource += `\nin ${source.fileName}:${source.pos.line + 1}:${source.pos.character + 1}\n\n${linePrefix}${brokenLine}\n${" ".repeat(linePrefix.length - 1)}|${" ".repeat(source.pos.character)}^`;
} catch (e) {
// Errors inside BaseError would be confusing
// so ignore errors here and fallback to the original message
}
}
dsherret marked this conversation as resolved.
Show resolved Hide resolved
super(messageWithSource);
this.message = messageWithSource;
this.source = source;
}
}

/** Thrown when there is a problem with a provided argument. */
export class ArgumentError extends BaseError {
constructor(argName: string, message: string) {
super(`Argument Error (${argName}): ${message}`);
constructor(argName: string, message: string, node?: MaybeTraceAbleNode) {
super(`Argument Error (${argName}): ${message}`, node);
}
}

/** Thrown when an argument is null or whitespace. */
export class ArgumentNullOrWhitespaceError extends ArgumentError {
constructor(argName: string) {
super(argName, "Cannot be null or whitespace.");
constructor(argName: string, node?: MaybeTraceAbleNode) {
super(argName, "Cannot be null or whitespace.", node);
}
}

/** Thrown when an argument is out of range. */
export class ArgumentOutOfRangeError extends ArgumentError {
constructor(argName: string, value: number, range: [number, number]) {
super(argName, `Range is ${range[0]} to ${range[1]}, but ${value} was provided.`);
constructor(argName: string, value: number, range: [number, number], node?: MaybeTraceAbleNode) {
super(argName, `Range is ${range[0]} to ${range[1]}, but ${value} was provided.`, node);
}
}

/** Thrown when an argument does not match an expected type. */
export class ArgumentTypeError extends ArgumentError {
constructor(argName: string, expectedType: string, actualType: string) {
super(argName, `Expected type '${expectedType}', but was '${actualType}'.`);
constructor(argName: string, expectedType: string, actualType: string, node?: MaybeTraceAbleNode) {
super(argName, `Expected type '${expectedType}', but was '${actualType}'.`, node);
}
}

Expand Down Expand Up @@ -67,15 +98,15 @@ export namespace errors {

/** Thrown when an action was taken that is not allowed. */
export class InvalidOperationError extends BaseError {
constructor(message: string) {
super(message);
constructor(message: string, node?: MaybeTraceAbleNode) {
super(message, node);
}
}

/** Thrown when a certain behaviour or feature has not been implemented. */
export class NotImplementedError extends BaseError {
constructor(message = "Not implemented.") {
super(message);
constructor(message = "Not implemented.", node?: MaybeTraceAbleNode) {
super(message, node);
}
}

Expand Down Expand Up @@ -148,8 +179,8 @@ export namespace errors {
* Gets an error saying that a feature is not implemented for a certain syntax kind.
* @param kind - Syntax kind that isn't implemented.
*/
export function throwNotImplementedForSyntaxKindError(kind: ts.SyntaxKind): never {
throw new NotImplementedError(`Not implemented feature for syntax kind '${getSyntaxKindName(kind)}'.`);
export function throwNotImplementedForSyntaxKindError(kind: ts.SyntaxKind, node?: MaybeTraceAbleNode): never {
throw new NotImplementedError(`Not implemented feature for syntax kind '${getSyntaxKindName(kind)}'.`, node);
}

/**
Expand All @@ -167,22 +198,22 @@ export namespace errors {
* @param value - Value to check.
* @param errorMessage - Error message to throw when not defined.
*/
export function throwIfNullOrUndefined<T>(value: T | undefined, errorMessage: string | (() => string)) {
export function throwIfNullOrUndefined<T>(value: T | undefined, errorMessage: string | (() => string), node?: MaybeTraceAbleNode) {
if (value == null)
throw new InvalidOperationError(typeof errorMessage === "string" ? errorMessage : errorMessage());
throw new InvalidOperationError(typeof errorMessage === "string" ? errorMessage : errorMessage(), node);
return value;
}

/**
* Throw if the value should have been the never type.
* @param value - Value to check.
*/
export function throwNotImplementedForNeverValueError(value: never): never {
export function throwNotImplementedForNeverValueError(value: never, sourceNode?: MaybeTraceAbleNode): never {
const node = value as any as { kind: number };
if (node != null && typeof node.kind === "number")
return throwNotImplementedForSyntaxKindError(node.kind);
return throwNotImplementedForSyntaxKindError(node.kind, sourceNode);

throw new NotImplementedError(`Not implemented value: ${JSON.stringify(value)}`);
throw new NotImplementedError(`Not implemented value: ${JSON.stringify(value)}`, sourceNode);
}

/**
Expand Down