Improve context for errors thrown by compiler

This commit introduces a custom error object to provide additional
context for errors throwing during parsing and compiling operations,
improving troubleshooting.

By integrating error context handling, the error messages become more
informative and user-friendly, providing sequence of trace with context
to aid in troubleshooting.

Changes include:

- Introduce custom error object that extends errors with contextual
  information. This replaces previous usages of `AggregateError` which
  is not displayed well by browsers when logged.
- Improve parsing functions to encapsulate error context with more
  details.
- Increase unit test coverage and refactor the related code to be more
  testable.
This commit is contained in:
undergroundwires
2024-05-25 13:55:30 +02:00
parent 7794846185
commit 4212c7b9e0
78 changed files with 3346 additions and 1268 deletions

View File

@@ -5,15 +5,21 @@ import type { IScript } from './IScript';
export class Category extends BaseEntity<number> implements ICategory {
private allSubScripts?: ReadonlyArray<IScript> = undefined;
constructor(
id: number,
public readonly name: string,
public readonly docs: ReadonlyArray<string>,
public readonly subCategories: ReadonlyArray<ICategory>,
public readonly scripts: ReadonlyArray<IScript>,
) {
super(id);
validateCategory(this);
public readonly name: string;
public readonly docs: ReadonlyArray<string>;
public readonly subCategories: ReadonlyArray<ICategory>;
public readonly scripts: ReadonlyArray<IScript>;
constructor(parameters: CategoryInitParameters) {
super(parameters.id);
validateParameters(parameters);
this.name = parameters.name;
this.docs = parameters.docs;
this.subCategories = parameters.subcategories;
this.scripts = parameters.scripts;
}
public includes(script: IScript): boolean {
@@ -28,6 +34,14 @@ export class Category extends BaseEntity<number> implements ICategory {
}
}
export interface CategoryInitParameters {
readonly id: number;
readonly name: string;
readonly docs: ReadonlyArray<string>;
readonly subcategories: ReadonlyArray<ICategory>;
readonly scripts: ReadonlyArray<IScript>;
}
function parseScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
return [
...category.scripts,
@@ -35,11 +49,11 @@ function parseScriptsRecursively(category: ICategory): ReadonlyArray<IScript> {
];
}
function validateCategory(category: ICategory) {
if (!category.name) {
function validateParameters(parameters: CategoryInitParameters) {
if (!parameters.name) {
throw new Error('missing name');
}
if (category.subCategories.length === 0 && category.scripts.length === 0) {
if (parameters.subcategories.length === 0 && parameters.scripts.length === 0) {
throw new Error('A category must have at least one sub-category or script');
}
}

View File

@@ -4,14 +4,21 @@ import type { IScript } from './IScript';
import type { IScriptCode } from './IScriptCode';
export class Script extends BaseEntity<string> implements IScript {
constructor(
public readonly name: string,
public readonly code: IScriptCode,
public readonly docs: ReadonlyArray<string>,
public readonly level?: RecommendationLevel,
) {
super(name);
validateLevel(level);
public readonly name: string;
public readonly code: IScriptCode;
public readonly docs: ReadonlyArray<string>;
public readonly level?: RecommendationLevel;
constructor(parameters: ScriptInitParameters) {
super(parameters.name);
this.name = parameters.name;
this.code = parameters.code;
this.docs = parameters.docs;
this.level = parameters.level;
validateLevel(parameters.level);
}
public canRevert(): boolean {
@@ -19,6 +26,13 @@ export class Script extends BaseEntity<string> implements IScript {
}
}
export interface ScriptInitParameters {
readonly name: string;
readonly code: IScriptCode;
readonly docs: ReadonlyArray<string>;
readonly level?: RecommendationLevel;
}
function validateLevel(level?: RecommendationLevel) {
if (level !== undefined && !(level in RecommendationLevel)) {
throw new Error(`invalid level: ${level}`);

View File

@@ -0,0 +1,10 @@
import { ScriptCode } from './ScriptCode';
import type { IScriptCode } from './IScriptCode';
export interface ScriptCodeFactory {
(
...args: ConstructorParameters<typeof ScriptCode>
): IScriptCode;
}
export const createScriptCode: ScriptCodeFactory = (...args) => new ScriptCode(...args);