Skip to content

Commit

Permalink
Merge pull request #7174 from QwikDev/v2-errors-refactor
Browse files Browse the repository at this point in the history
refactor: v2 errors
  • Loading branch information
shairez authored Dec 19, 2024
2 parents ccbb690 + 0ec45ce commit 8a83872
Show file tree
Hide file tree
Showing 23 changed files with 191 additions and 136 deletions.
6 changes: 3 additions & 3 deletions packages/qwik/src/core/client/dom-container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import type { QRL } from '../shared/qrl/qrl.public';
import { ERROR_CONTEXT, isRecoverable } from '../shared/error/error-handling';
import type { ContextId } from '../use/use-context';
import { EMPTY_ARRAY } from '../shared/utils/flyweight';
import { throwErrorAndStop } from '../shared/utils/log';
import {
ELEMENT_PROPS,
ELEMENT_SEQ,
Expand Down Expand Up @@ -66,12 +65,13 @@ import {
vnode_setProp,
type VNodeJournal,
} from './vnode';
import { QError, qError } from '../shared/error/error';

/** @public */
export function getDomContainer(element: Element | VNode): IClientContainer {
const qContainerElement = _getQContainerElement(element);
if (!qContainerElement) {
throwErrorAndStop('Unable to find q:container.');
throw qError(QError.containerNotFound);
}
return getDomContainerFromQContainerElement(qContainerElement!);
}
Expand Down Expand Up @@ -145,7 +145,7 @@ export class DomContainer extends _SharedContainer implements IClientContainer {
);
this.qContainer = element.getAttribute(QContainerAttr)!;
if (!this.qContainer) {
throwErrorAndStop("Element must have 'q:container' attribute.");
throw qError(QError.elementWithoutContainer);
}
this.$journal$ = [
// The first time we render we need to hoist the styles.
Expand Down
5 changes: 5 additions & 0 deletions packages/qwik/src/core/client/dom-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { DomContainer, getDomContainer } from './dom-container';
import { cleanup } from './vnode-diff';
import { QContainerAttr } from '../shared/utils/markers';
import type { RenderOptions, RenderResult } from './types';
import { qDev } from '../shared/utils/qdev';
import { QError, qError } from '../shared/error/error';

/**
* Render JSX.
Expand All @@ -32,6 +34,9 @@ export const render = async (
}
parent = child as Element;
}
if (qDev && parent.hasAttribute(QContainerAttr)) {
throw qError(QError.cannotRenderOverExistingContainer, [parent]);
}
(parent as Element).setAttribute(QContainerAttr, QContainerValue.RESUMED);

const container = getDomContainer(parent as HTMLElement) as DomContainer;
Expand Down
4 changes: 2 additions & 2 deletions packages/qwik/src/core/client/vnode-diff.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ import {
clearSubscriberEffectDependencies,
clearVNodeEffectDependencies,
} from '../signal/signal-subscriber';
import { throwErrorAndStop } from '../shared/utils/log';
import { serializeAttribute } from '../shared/utils/styles';
import { QError, qError } from '../shared/error/error';

export type ComponentQueue = Array<VNode>;

Expand Down Expand Up @@ -655,7 +655,7 @@ export const vnode_diff = (
if (elementName === 'textarea' && key === 'value') {
if (typeof value !== 'string') {
if (isDev) {
throwErrorAndStop('The value of the textarea must be a string');
throw qError(QError.wrongTextareaValue);
}
continue;
}
Expand Down
10 changes: 4 additions & 6 deletions packages/qwik/src/core/client/vnode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,6 @@ import { isDev } from '@qwik.dev/core/build';
import { qwikDebugToString } from '../debug';
import { assertDefined, assertEqual, assertFalse, assertTrue } from '../shared/error/assert';
import { isText } from '../shared/utils/element';
import { throwErrorAndStop } from '../shared/utils/log';
import {
ELEMENT_ID,
ELEMENT_KEY,
Expand Down Expand Up @@ -170,6 +169,7 @@ import {
vnode_getElementNamespaceFlags,
} from './vnode-namespace';
import { escapeHTML } from '../shared/utils/character-escaping';
import { QError, qError } from '../shared/error/error';

//////////////////////////////////////////////////////////////////////////////////////////////////////

Expand Down Expand Up @@ -1637,7 +1637,7 @@ export const vnode_getPropStartIndex = (vnode: VNode): number => {
} else if (type === VNodeFlags.Virtual) {
return VirtualVNodeProps.PROPS_OFFSET;
}
throw throwErrorAndStop('Invalid vnode type.');
throw qError(QError.invalidVNodeType, [type]);
};

export const vnode_propsToRecord = (vnode: VNode): Record<string, unknown> => {
Expand Down Expand Up @@ -1780,9 +1780,7 @@ function materializeFromVNodeData(
while (!isElement(child)) {
child = fastNextSibling(child);
if (!child) {
throwErrorAndStop(
'Materialize error: missing element: ' + vData + ' ' + peek() + ' ' + nextToConsumeIdx
);
throw qError(QError.materializeVNodeDataError, [vData, peek(), nextToConsumeIdx]);
}
}
// We pretend that style element's don't exist as they can get moved out.
Expand Down Expand Up @@ -1885,7 +1883,7 @@ export const vnode_getType = (vnode: VNode): 1 | 3 | 11 => {
} else if (type & VNodeFlags.Text) {
return 3 /* Text */;
}
throw throwErrorAndStop('Unknown vnode type: ' + type);
throw qError(QError.invalidVNodeType, [type]);
};

const isElement = (node: any): node is Element =>
Expand Down
159 changes: 100 additions & 59 deletions packages/qwik/src/core/shared/error/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,57 @@ export const codeToText = (code: number, ...parts: any[]): string => {
// Keep one error, one line to make it easier to search for the error message.
const MAP = [
'Error while serializing class or style attributes', // 0
'Can not serialize a HTML Node that is not an Element', // 1
'Runtime but no instance found on element.', // 2
'Only primitive and object literals can be serialized', // 3
'Crash while rendering', // 4
'', // 1 unused
'', // 2 unused
'Only primitive and object literals can be serialized. {{0}}', // 3
'', // 4 unused
'You can render over a existing q:container. Skipping render().', // 5
'Set property {{0}}', // 6
"Only function's and 'string's are supported.", // 7
"Only objects can be wrapped in 'QObject'", // 8
`Only objects literals can be wrapped in 'QObject'`, // 9
'', // 6 unused
'', // 7 unused
'', // 8 unused
'', // 9 unused
'QRL is not a function', // 10
'Dynamic import not found', // 11
'Unknown type argument', // 12
`Actual value for useContext({{0}}) can not be found, make sure some ancestor component has set a value using useContextProvider(). In the browser make sure that the context was used during SSR so its state was serialized.`, // 13
"Invoking 'use*()' method outside of invocation context.", // 14
'Cant access renderCtx for existing context', // 15
'Cant access document for existing context', // 16
'props are immutable', // 17
'<div> component can only be used at the root of a Qwik component$()', // 18
'Props are immutable by default.', // 19
'', // 15 unused
'', // 16 unused
'', // 17 unused
'', // 18 unused
'', // 19 unused
`Calling a 'use*()' method outside 'component$(() => { HERE })' is not allowed. 'use*()' methods provide hooks to the 'component$' state and lifecycle, ie 'use' hooks can only be called synchronously within the 'component$' function or another 'use' method.\nSee https://qwik.dev/docs/components/tasks/#use-method-rules`, // 20
'Container is already paused. Skipping', // 21
'', // 22 -- unused
'When rendering directly on top of Document, the root node must be a <html>', // 23
'A <html> node must have 2 children. The first one <head> and the second one a <body>', // 24
'Invalid JSXNode type "{{0}}". It must be either a function or a string. Found:', // 25
'Tracking value changes can only be done to useStore() objects and component props', // 26
'Missing Object ID for captured object', // 27
'', // 21 unused
'', // 22 unused
'', // 23 unused
'', // 24 unused
'', // 25 unused
'', // 26 unused
'', // 27 unused
'The provided Context reference "{{0}}" is not a valid context created by createContextId()', // 28
'<html> is the root container, it can not be rendered inside a component', // 29
'SsrError(tag): {{0}}', // 29
'QRLs can not be resolved because it does not have an attached container. This means that the QRL does not know where it belongs inside the DOM, so it cant dynamically import() from a relative path.', // 30
'QRLs can not be dynamically resolved, because it does not have a chunk path', // 31
'The JSX ref attribute must be a Signal', // 32
'Serialization Error: Deserialization of data type {{0}} is not implemented', // 33
'Serialization Error: Expected vnode for ref prop, but got {{0}}', // 34
'Serialization Error: Cannot allocate data type {{0}}', // 35
'Serialization Error: Missing root id for {{0}}', // 36
'Serialization Error: Serialization of data type {{0}} is not implemented', // 37
'Serialization Error: Unvisited {{0}}', // 38
'Serialization Error: Missing QRL chunk for {{0}}', // 39
'The value of the textarea must be a string', // 40
'Unable to find q:container', // 41
"Element must have 'q:container' attribute.", // 42
'Unknown vnode type {{0}}.', // 43
'Materialize error: missing element: {{0}} {{1}} {{2}}', // 44
'SsrError: {{0}}', // 45
'Cannot coerce a Signal, use `.value` instead', // 46
'useComputedSignal$ QRL {{0}} {{1}} returned a Promise', // 47
'ComputedSignal is read-only', // 48
'WrappedSignal is read-only', // 49
'SsrError: Promises not expected here.', // 50
'Attribute value is unsafe for SSR', // 51
];
let text = MAP[code] ?? '';
if (parts.length) {
Expand All @@ -49,47 +68,69 @@ export const codeToText = (code: number, ...parts: any[]): string => {
return v;
});
}
return `Code(${code}): ${text}`;
return `Code(Q${code}): ${text}`;
} else {
// cute little hack to give roughly the correct line number. Update the line number if it shifts.
return `Code(${code}) https://github.com/QwikDev/qwik/blob/main/packages/qwik/src/core/error/error.ts#L${8 + code}`;
return `Code(Q${code}) https://github.com/QwikDev/qwik/blob/main/packages/qwik/src/core/error/error.ts#L${8 + code}`;
}
};

export const QError_stringifyClassOrStyle = 0;
export const QError_cannotSerializeNode = 1;
export const QError_runtimeQrlNoElement = 2;
export const QError_verifySerializable = 3;
export const QError_errorWhileRendering = 4;
export const QError_cannotRenderOverExistingContainer = 5;
export const QError_setProperty = 6;
export const QError_qrlOrError = 7;
export const QError_onlyObjectWrapped = 8;
export const QError_onlyLiteralWrapped = 9;
export const QError_qrlIsNotFunction = 10;
export const QError_dynamicImportFailed = 11;
export const QError_unknownTypeArgument = 12;
export const QError_notFoundContext = 13;
export const QError_useMethodOutsideContext = 14;
export const QError_missingRenderCtx = 15;
export const QError_missingDoc = 16;
export const QError_immutableProps = 17;
export const QError_hostCanOnlyBeAtRoot = 18;
export const QError_immutableJsxProps = 19;
export const QError_useInvokeContext = 20;
export const QError_containerAlreadyPaused = 21;
export const QError_unused_please_reuse = 22;
export const QError_rootNodeMustBeHTML = 23;
export const QError_strictHTMLChildren = 24;
export const QError_invalidJsxNodeType = 25;
export const QError_trackUseStore = 26;
export const QError_missingObjectId = 27;
export const QError_invalidContext = 28;
export const QError_canNotRenderHTML = 29;
export const QError_qrlMissingContainer = 30;
export const QError_qrlMissingChunk = 31;
export const QError_invalidRefValue = 32;
export const qError = (code: number, ...parts: any[]): Error => {
const text = codeToText(code, ...parts);
return logErrorAndStop(text, ...parts);
export const enum QError {
stringifyClassOrStyle = 0,
UNUSED_1 = 1,
UNUSED_2 = 2,
verifySerializable = 3,
UNUSED_4 = 4,
cannotRenderOverExistingContainer = 5,
UNUSED_6 = 6,
UNUSED_7 = 7,
UNUSED_8 = 8,
UNUSED_9 = 9,
qrlIsNotFunction = 10,
dynamicImportFailed = 11,
unknownTypeArgument = 12,
notFoundContext = 13,
useMethodOutsideContext = 14,
UNUSED_15 = 15,
UNUSED_16 = 16,
UNUSED_17 = 17,
UNUSED_18 = 18,
UNUSED_19 = 19,
useInvokeContext = 20,
UNUSED_21 = 21,
UNUSED_22 = 22,
UNUSED_23 = 23,
UNUSED_24 = 24,
UNUSED_25 = 25,
UNUSED_26 = 26,
UNUSED_27 = 27,
invalidContext = 28,
tagError = 29,
qrlMissingContainer = 30,
qrlMissingChunk = 31,
invalidRefValue = 32,
serializeErrorNotImplemented = 33,
serializeErrorExpectedVNode = 34,
serializeErrorCannotAllocate = 35,
serializeErrorMissingRootId = 36,
serializeErrorUnknownType = 37,
serializeErrorUnvisited = 38,
serializeErrorMissingChunk = 39,
wrongTextareaValue = 40,
containerNotFound = 41,
elementWithoutContainer = 42,
invalidVNodeType = 43,
materializeVNodeDataError = 44,
serverHostMismatch = 45,
cannotCoerceSignal = 46,
computedNotSync = 47,
computedReadOnly = 48,
wrappedReadOnly = 49,
promisesNotExpected = 50,
unsafeAttr = 51,
}

export const qError = (code: number, errorMessageArgs: any[] = []): Error => {
const text = codeToText(code, ...errorMessageArgs);
return logErrorAndStop(text, ...errorMessageArgs);
};
6 changes: 3 additions & 3 deletions packages/qwik/src/core/shared/platform/platform.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// keep this import from core/build so the cjs build works
import { isServer } from '@qwik.dev/core/build';
import { qError, QError_qrlMissingChunk, QError_qrlMissingContainer } from '../error/error';
import { QError, qError } from '../error/error';
import { getSymbolHash } from '../qrl/qrl-class';
import { qDynamicPlatform } from '../utils/qdev';
import type { CorePlatform } from './types';
Expand All @@ -17,10 +17,10 @@ export const createPlatform = (): CorePlatform => {
}
}
if (!url) {
throw qError(QError_qrlMissingChunk, symbolName);
throw qError(QError.qrlMissingChunk, [symbolName]);
}
if (!containerEl) {
throw qError(QError_qrlMissingContainer, url, symbolName);
throw qError(QError.qrlMissingContainer, [url, symbolName]);
}
const urlDoc = toUrl(containerEl.ownerDocument, containerEl, url).toString();
const urlCopy = new URL(urlDoc);
Expand Down
4 changes: 2 additions & 2 deletions packages/qwik/src/core/shared/qrl/qrl-class.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isDev } from '@qwik.dev/core/build';
import { assertDefined } from '../error/assert';
import { qError, QError_qrlIsNotFunction } from '../error/error';
import { QError, qError } from '../error/error';
import { getPlatform, isServerPlatform } from '../platform/platform';
import { verifySerializable } from '../utils/serialize-utils';
import {
Expand Down Expand Up @@ -109,7 +109,7 @@ export const createQRL = <TYPE>(
return (...args: QrlArgs<TYPE>): QrlReturn<TYPE> =>
maybeThen(resolveLazy(), (fn) => {
if (!isFunction(fn)) {
throw qError(QError_qrlIsNotFunction);
throw qError(QError.qrlIsNotFunction);
}
if (beforeFn && beforeFn() === false) {
return;
Expand Down
6 changes: 3 additions & 3 deletions packages/qwik/src/core/shared/qrl/qrl.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { QError_dynamicImportFailed, QError_unknownTypeArgument, qError } from '../error/error';
import { QError, qError } from '../error/error';
import { EMPTY_ARRAY } from '../utils/flyweight';
import { qSerialize } from '../utils/qdev';
import { isFunction, isString } from '../utils/types';
Expand Down Expand Up @@ -66,13 +66,13 @@ export const qrl = <T = any>(
chunk = match[1];
}
} else {
throw qError(QError_dynamicImportFailed, srcCode);
throw qError(QError.dynamicImportFailed, [srcCode]);
}
}
} else if (isString(chunkOrFn)) {
chunk = chunkOrFn;
} else {
throw qError(QError_unknownTypeArgument, chunkOrFn);
throw qError(QError.unknownTypeArgument, [chunkOrFn]);
}

if (!announcedQRL.has(symbol)) {
Expand Down
5 changes: 3 additions & 2 deletions packages/qwik/src/core/shared/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ import type { QRLInternal } from './qrl/qrl-class';
import type { JSXOutput } from './jsx/types/jsx-node';
import { Task, TaskFlags, cleanupTask, runTask, type TaskFn } from '../use/use-task';
import { runResource, type ResourceDescriptor } from '../use/use-resource';
import { logWarn, throwErrorAndStop } from './utils/log';
import { logWarn } from './utils/log';
import { isPromise, maybeThenPassError, retryOnPromise, safeCall } from './utils/promises';
import type { ValueOrPromise } from './utils/types';
import { isDomContainer } from '../client/dom-container';
Expand Down Expand Up @@ -115,6 +115,7 @@ import { QScopedStyle } from './utils/markers';
import { addComponentStylePrefix } from './utils/scoped-styles';
import { type WrappedSignal, type ComputedSignal, triggerEffects } from '../signal/signal';
import type { TargetType } from '../signal/store';
import { QError, qError } from './error/error';

// Turn this on to get debug output of what the scheduler is doing.
const DEBUG: boolean = false;
Expand Down Expand Up @@ -496,7 +497,7 @@ function choreComparator(a: Chore, b: Chore, shouldThrowOnHostMismatch: boolean)
This can lead to inconsistencies between Server-Side Rendering (SSR) and Client-Side Rendering (CSR).
Problematic Node: ${aHost.toString()}`;
if (shouldThrowOnHostMismatch) {
throwErrorAndStop(errorMessage);
throw qError(QError.serverHostMismatch, [errorMessage]);
}
logWarn(errorMessage);
return null;
Expand Down
Loading

0 comments on commit 8a83872

Please sign in to comment.