@knighted/jsx 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cjs/debug/diagnostics.cjs +57 -0
- package/dist/cjs/debug/diagnostics.d.cts +6 -0
- package/dist/cjs/debug/index.cjs +7 -0
- package/dist/cjs/debug/index.d.cts +2 -0
- package/dist/cjs/internal/attribute-resolution.cjs +55 -0
- package/dist/cjs/internal/attribute-resolution.d.cts +15 -0
- package/dist/cjs/internal/dev-environment.cjs +41 -0
- package/dist/cjs/internal/dev-environment.d.cts +4 -0
- package/dist/cjs/internal/event-bindings.cjs +93 -0
- package/dist/cjs/internal/event-bindings.d.cts +22 -0
- package/dist/cjs/internal/template-diagnostics.cjs +171 -0
- package/dist/cjs/internal/template-diagnostics.d.cts +13 -0
- package/dist/cjs/jsx.cjs +9 -110
- package/dist/cjs/loader/jsx.cjs +92 -20
- package/dist/cjs/loader/jsx.d.cts +6 -1
- package/dist/cjs/node/bootstrap.cjs +19 -19
- package/dist/cjs/node/bootstrap.d.cts +2 -1
- package/dist/cjs/node/debug/index.cjs +6 -0
- package/dist/cjs/node/debug/index.d.cts +2 -0
- package/dist/cjs/node/index.cjs +1 -1
- package/dist/cjs/react/react-jsx.cjs +1 -1
- package/dist/cjs/runtime/shared.cjs +41 -22
- package/dist/cjs/runtime/shared.d.cts +5 -2
- package/dist/debug/diagnostics.d.ts +6 -0
- package/dist/debug/diagnostics.js +52 -0
- package/dist/debug/index.d.ts +2 -0
- package/dist/debug/index.js +3 -0
- package/dist/internal/attribute-resolution.d.ts +15 -0
- package/dist/internal/attribute-resolution.js +50 -0
- package/dist/internal/dev-environment.d.ts +4 -0
- package/dist/internal/dev-environment.js +34 -0
- package/dist/internal/event-bindings.d.ts +22 -0
- package/dist/internal/event-bindings.js +87 -0
- package/dist/internal/template-diagnostics.d.ts +13 -0
- package/dist/internal/template-diagnostics.js +167 -0
- package/dist/jsx.js +9 -110
- package/dist/lite/debug/diagnostics.js +1 -0
- package/dist/lite/debug/index.js +8 -0
- package/dist/lite/index.js +8 -4
- package/dist/lite/node/debug/index.js +8 -0
- package/dist/lite/node/index.js +8 -4
- package/dist/lite/node/react/index.js +7 -3
- package/dist/lite/react/index.js +7 -3
- package/dist/loader/jsx.d.ts +6 -1
- package/dist/loader/jsx.js +92 -20
- package/dist/node/bootstrap.d.ts +2 -1
- package/dist/node/bootstrap.js +19 -19
- package/dist/node/debug/index.d.ts +2 -0
- package/dist/node/debug/index.js +6 -0
- package/dist/node/index.js +1 -1
- package/dist/react/react-jsx.js +2 -2
- package/dist/runtime/shared.d.ts +5 -2
- package/dist/runtime/shared.js +39 -21
- package/package.json +40 -8
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ A runtime JSX template tag backed by the [`oxc-parser`](https://github.com/oxc-p
|
|
|
19
19
|
- [Loader integration](#loader-integration)
|
|
20
20
|
- [Node / SSR usage](#node--ssr-usage)
|
|
21
21
|
- [Browser usage](#browser-usage)
|
|
22
|
+
- [Development diagnostics](docs/development-diagnostics.md)
|
|
22
23
|
- [TypeScript plugin](docs/ts-plugin.md)
|
|
23
24
|
- [TypeScript guide](docs/typescript.md)
|
|
24
25
|
- [Component testing](docs/testing.md)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.disableJsxDebugDiagnostics = exports.enableJsxDebugDiagnostics = void 0;
|
|
4
|
+
const attribute_resolution_js_1 = require("../internal/attribute-resolution.cjs");
|
|
5
|
+
const event_bindings_js_1 = require("../internal/event-bindings.cjs");
|
|
6
|
+
const dev_environment_js_1 = require("../internal/dev-environment.cjs");
|
|
7
|
+
const isAsciiLowercase = (char) => char >= 'a' && char <= 'z';
|
|
8
|
+
let diagnosticsMode = 'env';
|
|
9
|
+
const shouldRunDiagnostics = () => diagnosticsMode === 'always' || (diagnosticsMode === 'env' && (0, dev_environment_js_1.isDevEnvironment)());
|
|
10
|
+
const shouldForceWarnings = () => diagnosticsMode === 'always';
|
|
11
|
+
const attributeDiagnostics = {
|
|
12
|
+
warnLowercaseEventProp(name) {
|
|
13
|
+
if (!shouldRunDiagnostics()) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (!name.startsWith('on') || name.startsWith('on:') || name.length < 3) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const indicator = name[2] ?? '';
|
|
20
|
+
if (!isAsciiLowercase(indicator)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const suggestion = `${name.slice(0, 2)}${indicator.toUpperCase()}${name.slice(3)}`;
|
|
24
|
+
(0, dev_environment_js_1.emitDevWarning)(`Use camelCase DOM event props when targeting runtime jsx templates. Received "${name}"; did you mean "${suggestion}"?`, shouldForceWarnings());
|
|
25
|
+
},
|
|
26
|
+
ensureValidDangerouslySetInnerHTML(value) {
|
|
27
|
+
if (!shouldRunDiagnostics()) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
31
|
+
throw (0, dev_environment_js_1.createDevError)('dangerouslySetInnerHTML expects an object with a string __html field.');
|
|
32
|
+
}
|
|
33
|
+
const html = value.__html;
|
|
34
|
+
if (typeof html !== 'string') {
|
|
35
|
+
throw (0, dev_environment_js_1.createDevError)(`dangerouslySetInnerHTML.__html must be a string but received ${(0, dev_environment_js_1.describeValue)(html)}.`);
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
const eventDiagnostics = {
|
|
40
|
+
onInvalidHandler(propName, value) {
|
|
41
|
+
if (!shouldRunDiagnostics()) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
throw (0, dev_environment_js_1.createDevError)(`The "${propName}" prop expects a function, EventListenerObject, or descriptor ({ handler }) but received ${(0, dev_environment_js_1.describeValue)(value)}.`);
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
const enableJsxDebugDiagnostics = (options) => {
|
|
48
|
+
diagnosticsMode = options?.mode ?? 'env';
|
|
49
|
+
(0, attribute_resolution_js_1.setAttributeDiagnosticsHooks)(attributeDiagnostics);
|
|
50
|
+
(0, event_bindings_js_1.setEventDiagnosticsHooks)(eventDiagnostics);
|
|
51
|
+
};
|
|
52
|
+
exports.enableJsxDebugDiagnostics = enableJsxDebugDiagnostics;
|
|
53
|
+
const disableJsxDebugDiagnostics = () => {
|
|
54
|
+
(0, attribute_resolution_js_1.setAttributeDiagnosticsHooks)(null);
|
|
55
|
+
(0, event_bindings_js_1.setEventDiagnosticsHooks)(null);
|
|
56
|
+
};
|
|
57
|
+
exports.disableJsxDebugDiagnostics = disableJsxDebugDiagnostics;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type JsxDiagnosticsMode = 'env' | 'always';
|
|
2
|
+
export type EnableJsxDebugDiagnosticsOptions = {
|
|
3
|
+
mode?: JsxDiagnosticsMode;
|
|
4
|
+
};
|
|
5
|
+
export declare const enableJsxDebugDiagnostics: (options?: EnableJsxDebugDiagnosticsOptions) => void;
|
|
6
|
+
export declare const disableJsxDebugDiagnostics: () => void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.jsx = void 0;
|
|
4
|
+
const diagnostics_js_1 = require("./diagnostics.cjs");
|
|
5
|
+
(0, diagnostics_js_1.enableJsxDebugDiagnostics)({ mode: 'always' });
|
|
6
|
+
var jsx_js_1 = require("../jsx.cjs");
|
|
7
|
+
Object.defineProperty(exports, "jsx", { enumerable: true, get: function () { return jsx_js_1.jsx; } });
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createResolveAttributes = exports.setAttributeDiagnosticsHooks = void 0;
|
|
4
|
+
let attributeDiagnostics = null;
|
|
5
|
+
const setAttributeDiagnosticsHooks = (hooks) => {
|
|
6
|
+
attributeDiagnostics = hooks;
|
|
7
|
+
};
|
|
8
|
+
exports.setAttributeDiagnosticsHooks = setAttributeDiagnosticsHooks;
|
|
9
|
+
const warnLowercaseEventProp = (name) => {
|
|
10
|
+
attributeDiagnostics?.warnLowercaseEventProp?.(name);
|
|
11
|
+
};
|
|
12
|
+
const ensureValidDangerouslySetInnerHTML = (value) => {
|
|
13
|
+
attributeDiagnostics?.ensureValidDangerouslySetInnerHTML?.(value);
|
|
14
|
+
};
|
|
15
|
+
const createResolveAttributes = (deps) => {
|
|
16
|
+
const { getIdentifierName, evaluateExpressionWithNamespace } = deps;
|
|
17
|
+
return (attributes, ctx, namespace) => {
|
|
18
|
+
const props = {};
|
|
19
|
+
const assignProp = (propName, propValue) => {
|
|
20
|
+
if (propName === 'dangerouslySetInnerHTML') {
|
|
21
|
+
ensureValidDangerouslySetInnerHTML(propValue);
|
|
22
|
+
}
|
|
23
|
+
props[propName] = propValue;
|
|
24
|
+
};
|
|
25
|
+
attributes.forEach(attribute => {
|
|
26
|
+
if (attribute.type === 'JSXSpreadAttribute') {
|
|
27
|
+
const spreadValue = evaluateExpressionWithNamespace(attribute.argument, ctx, namespace);
|
|
28
|
+
if (spreadValue &&
|
|
29
|
+
typeof spreadValue === 'object' &&
|
|
30
|
+
!Array.isArray(spreadValue)) {
|
|
31
|
+
Object.assign(props, spreadValue);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
const name = getIdentifierName(attribute.name);
|
|
36
|
+
warnLowercaseEventProp(name);
|
|
37
|
+
if (!attribute.value) {
|
|
38
|
+
assignProp(name, true);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (attribute.value.type === 'Literal') {
|
|
42
|
+
assignProp(name, attribute.value.value);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (attribute.value.type === 'JSXExpressionContainer') {
|
|
46
|
+
if (attribute.value.expression.type === 'JSXEmptyExpression') {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
assignProp(name, evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace));
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return props;
|
|
53
|
+
};
|
|
54
|
+
};
|
|
55
|
+
exports.createResolveAttributes = createResolveAttributes;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Expression, JSXAttribute, JSXElement, JSXFragment, JSXSpreadAttribute } from '@oxc-project/types';
|
|
2
|
+
import type { TemplateComponent, TemplateContext } from '../runtime/shared.cjs';
|
|
3
|
+
export type AttributeDiagnosticsHooks = {
|
|
4
|
+
warnLowercaseEventProp?: (name: string) => void;
|
|
5
|
+
ensureValidDangerouslySetInnerHTML?: (value: unknown) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare const setAttributeDiagnosticsHooks: (hooks: AttributeDiagnosticsHooks | null) => void;
|
|
8
|
+
export type Namespace = 'svg' | null;
|
|
9
|
+
export type EvaluateExpressionWithNamespace<TComponent extends TemplateComponent> = (expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>, namespace: Namespace) => unknown;
|
|
10
|
+
export type ResolveAttributesDependencies<TComponent extends TemplateComponent> = {
|
|
11
|
+
getIdentifierName: (name: JSXAttribute['name']) => string;
|
|
12
|
+
evaluateExpressionWithNamespace: EvaluateExpressionWithNamespace<TComponent>;
|
|
13
|
+
};
|
|
14
|
+
export type ResolveAttributesFn<TComponent extends TemplateComponent> = (attributes: (JSXAttribute | JSXSpreadAttribute)[], ctx: TemplateContext<TComponent>, namespace: Namespace) => Record<string, unknown>;
|
|
15
|
+
export declare const createResolveAttributes: <TComponent extends TemplateComponent>(deps: ResolveAttributesDependencies<TComponent>) => ResolveAttributesFn<TComponent>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.emitDevWarning = exports.createDevError = exports.describeValue = exports.isDevEnvironment = void 0;
|
|
4
|
+
const DEV_PREFIX = '@knighted/jsx';
|
|
5
|
+
const formatDevMessage = (message) => `${DEV_PREFIX}: ${message}`;
|
|
6
|
+
const isDevEnvironment = () => typeof process !== 'undefined' && process.env?.KNIGHTED_JSX_DEBUG === '1';
|
|
7
|
+
exports.isDevEnvironment = isDevEnvironment;
|
|
8
|
+
const describeValue = (value) => {
|
|
9
|
+
if (value === null) {
|
|
10
|
+
return 'null';
|
|
11
|
+
}
|
|
12
|
+
if (value === undefined) {
|
|
13
|
+
return 'undefined';
|
|
14
|
+
}
|
|
15
|
+
if (typeof value === 'function') {
|
|
16
|
+
return value.name ? `function ${value.name}` : 'function';
|
|
17
|
+
}
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
return 'array';
|
|
20
|
+
}
|
|
21
|
+
if (typeof value === 'object') {
|
|
22
|
+
const ctor = value.constructor;
|
|
23
|
+
if (ctor && typeof ctor.name === 'string' && ctor.name && ctor.name !== 'Object') {
|
|
24
|
+
return `${ctor.name} instance`;
|
|
25
|
+
}
|
|
26
|
+
return 'object';
|
|
27
|
+
}
|
|
28
|
+
return typeof value;
|
|
29
|
+
};
|
|
30
|
+
exports.describeValue = describeValue;
|
|
31
|
+
const createDevError = (message) => new Error(formatDevMessage(message));
|
|
32
|
+
exports.createDevError = createDevError;
|
|
33
|
+
const emitDevWarning = (message, force = false) => {
|
|
34
|
+
if (!force && !(0, exports.isDevEnvironment)()) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (typeof console !== 'undefined' && typeof console.warn === 'function') {
|
|
38
|
+
console.warn(formatDevMessage(message));
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
exports.emitDevWarning = emitDevWarning;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveEventHandlerValue = exports.parseEventPropName = exports.setEventDiagnosticsHooks = void 0;
|
|
4
|
+
let eventDiagnostics = null;
|
|
5
|
+
const setEventDiagnosticsHooks = (hooks) => {
|
|
6
|
+
eventDiagnostics = hooks;
|
|
7
|
+
};
|
|
8
|
+
exports.setEventDiagnosticsHooks = setEventDiagnosticsHooks;
|
|
9
|
+
const captureSuffix = 'Capture';
|
|
10
|
+
const stripCaptureSuffix = (rawName) => {
|
|
11
|
+
if (rawName.endsWith(captureSuffix)) {
|
|
12
|
+
return { eventName: rawName.slice(0, -captureSuffix.length), capture: true };
|
|
13
|
+
}
|
|
14
|
+
return { eventName: rawName, capture: false };
|
|
15
|
+
};
|
|
16
|
+
const parseEventPropName = (name) => {
|
|
17
|
+
if (!name.startsWith('on')) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
if (name.startsWith('on:')) {
|
|
21
|
+
const raw = name.slice(3);
|
|
22
|
+
if (!raw) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const parsed = stripCaptureSuffix(raw);
|
|
26
|
+
if (!parsed.eventName) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
return parsed;
|
|
30
|
+
}
|
|
31
|
+
const raw = name.slice(2);
|
|
32
|
+
if (!raw) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const parsed = stripCaptureSuffix(raw);
|
|
36
|
+
if (!parsed.eventName) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
eventName: parsed.eventName.toLowerCase(),
|
|
41
|
+
capture: parsed.capture,
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
exports.parseEventPropName = parseEventPropName;
|
|
45
|
+
const isEventListenerObject = (value) => {
|
|
46
|
+
if (!value || typeof value !== 'object') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return ('handleEvent' in value &&
|
|
50
|
+
typeof value.handleEvent === 'function');
|
|
51
|
+
};
|
|
52
|
+
const isEventHandlerDescriptor = (value) => {
|
|
53
|
+
if (!value || typeof value !== 'object' || !('handler' in value)) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const handler = value.handler;
|
|
57
|
+
if (typeof handler === 'function') {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
return isEventListenerObject(handler);
|
|
61
|
+
};
|
|
62
|
+
const throwInvalidHandlerError = (propName, value) => {
|
|
63
|
+
eventDiagnostics?.onInvalidHandler?.(propName, value);
|
|
64
|
+
};
|
|
65
|
+
const resolveEventHandlerValue = (propName, value) => {
|
|
66
|
+
if (typeof value === 'function' || isEventListenerObject(value)) {
|
|
67
|
+
return { listener: value };
|
|
68
|
+
}
|
|
69
|
+
if (!isEventHandlerDescriptor(value)) {
|
|
70
|
+
throwInvalidHandlerError(propName, value);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const descriptor = value;
|
|
74
|
+
let options = descriptor.options ? { ...descriptor.options } : undefined;
|
|
75
|
+
const assignOption = (key, optionValue) => {
|
|
76
|
+
if (optionValue === undefined || optionValue === null) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!options) {
|
|
80
|
+
options = {};
|
|
81
|
+
}
|
|
82
|
+
options[key] = optionValue;
|
|
83
|
+
};
|
|
84
|
+
assignOption('capture', descriptor.capture);
|
|
85
|
+
assignOption('once', descriptor.once);
|
|
86
|
+
assignOption('passive', descriptor.passive);
|
|
87
|
+
assignOption('signal', descriptor.signal ?? undefined);
|
|
88
|
+
return {
|
|
89
|
+
listener: descriptor.handler,
|
|
90
|
+
options,
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
exports.resolveEventHandlerValue = resolveEventHandlerValue;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type EventDiagnosticsHooks = {
|
|
2
|
+
onInvalidHandler?: (propName: string, value: unknown) => void;
|
|
3
|
+
};
|
|
4
|
+
export declare const setEventDiagnosticsHooks: (hooks: EventDiagnosticsHooks | null) => void;
|
|
5
|
+
export type ParsedEventBinding = {
|
|
6
|
+
eventName: string;
|
|
7
|
+
capture: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const parseEventPropName: (name: string) => ParsedEventBinding | null;
|
|
10
|
+
export type EventHandlerDescriptor = {
|
|
11
|
+
handler: EventListenerOrEventListenerObject;
|
|
12
|
+
capture?: boolean;
|
|
13
|
+
once?: boolean;
|
|
14
|
+
passive?: boolean;
|
|
15
|
+
signal?: AbortSignal | null;
|
|
16
|
+
options?: AddEventListenerOptions;
|
|
17
|
+
};
|
|
18
|
+
export type ResolvedEventHandler = {
|
|
19
|
+
listener: EventListenerOrEventListenerObject;
|
|
20
|
+
options?: AddEventListenerOptions;
|
|
21
|
+
};
|
|
22
|
+
export declare const resolveEventHandlerValue: (propName: string, value: unknown) => ResolvedEventHandler | null;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatTaggedTemplateParserError = void 0;
|
|
4
|
+
const DEFAULT_LABEL = 'oxc-parser';
|
|
5
|
+
const buildTemplateDisplaySource = (templates) => {
|
|
6
|
+
const raw = templates.raw ?? templates;
|
|
7
|
+
let source = raw[0] ?? '';
|
|
8
|
+
const spans = [];
|
|
9
|
+
for (let idx = 0; idx < raw.length - 1; idx++) {
|
|
10
|
+
const label = '${expr#' + idx + '}';
|
|
11
|
+
const templateStart = source.length;
|
|
12
|
+
source += label;
|
|
13
|
+
const templateEnd = source.length;
|
|
14
|
+
spans.push({ index: idx, templateStart, templateEnd, label });
|
|
15
|
+
source += raw[idx + 1] ?? '';
|
|
16
|
+
}
|
|
17
|
+
return { source, spans };
|
|
18
|
+
};
|
|
19
|
+
const combineExpressionSpans = (diagnostics, templateSpans) => {
|
|
20
|
+
const templateSpanMap = new Map();
|
|
21
|
+
templateSpans.forEach(span => {
|
|
22
|
+
templateSpanMap.set(span.index, span);
|
|
23
|
+
});
|
|
24
|
+
return diagnostics.expressionRanges
|
|
25
|
+
.map(span => {
|
|
26
|
+
const templateSpan = templateSpanMap.get(span.index);
|
|
27
|
+
if (!templateSpan) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
const sourceLength = Math.max(0, span.sourceEnd - span.sourceStart);
|
|
31
|
+
const templateLength = Math.max(0, templateSpan.templateEnd - templateSpan.templateStart);
|
|
32
|
+
return {
|
|
33
|
+
sourceStart: span.sourceStart,
|
|
34
|
+
sourceEnd: span.sourceEnd,
|
|
35
|
+
templateStart: templateSpan.templateStart,
|
|
36
|
+
templateEnd: templateSpan.templateEnd,
|
|
37
|
+
delta: templateLength - sourceLength,
|
|
38
|
+
};
|
|
39
|
+
})
|
|
40
|
+
.filter((span) => Boolean(span))
|
|
41
|
+
.sort((a, b) => a.sourceStart - b.sourceStart);
|
|
42
|
+
};
|
|
43
|
+
const mapIndexToTemplate = (index, spans, templateLength) => {
|
|
44
|
+
if (!Number.isFinite(index) || index <= 0) {
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
let delta = 0;
|
|
48
|
+
for (const span of spans) {
|
|
49
|
+
if (index < span.sourceStart) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
if (index < span.sourceEnd) {
|
|
53
|
+
const relative = Math.max(0, index - span.sourceStart);
|
|
54
|
+
const templateSpanLength = Math.max(0, span.templateEnd - span.templateStart);
|
|
55
|
+
if (templateSpanLength === 0) {
|
|
56
|
+
return span.templateStart;
|
|
57
|
+
}
|
|
58
|
+
const clamped = Math.min(relative, Math.max(0, templateSpanLength - 1));
|
|
59
|
+
return span.templateStart + clamped;
|
|
60
|
+
}
|
|
61
|
+
delta += span.delta;
|
|
62
|
+
}
|
|
63
|
+
const mapped = index + delta;
|
|
64
|
+
if (mapped <= 0) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
if (mapped >= templateLength) {
|
|
68
|
+
return templateLength;
|
|
69
|
+
}
|
|
70
|
+
return mapped;
|
|
71
|
+
};
|
|
72
|
+
const getLineAndColumnFromIndex = (source, index) => {
|
|
73
|
+
const limit = Math.max(0, Math.min(index, source.length));
|
|
74
|
+
let line = 1;
|
|
75
|
+
let column = 1;
|
|
76
|
+
for (let idx = 0; idx < limit; idx++) {
|
|
77
|
+
if (source.charCodeAt(idx) === 10) {
|
|
78
|
+
line++;
|
|
79
|
+
column = 1;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
column++;
|
|
83
|
+
}
|
|
84
|
+
return { line, column };
|
|
85
|
+
};
|
|
86
|
+
const computeLineOffsets = (lines) => {
|
|
87
|
+
const offsets = [];
|
|
88
|
+
let cursor = 0;
|
|
89
|
+
lines.forEach((line, index) => {
|
|
90
|
+
offsets.push(cursor);
|
|
91
|
+
cursor += line.length;
|
|
92
|
+
if (index < lines.length - 1) {
|
|
93
|
+
cursor += 1;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
return offsets;
|
|
97
|
+
};
|
|
98
|
+
const createPointerLine = (lineNumber, lineText, lineStartOffset, rangeStart, rangeEnd, startLine, endLine) => {
|
|
99
|
+
const lineEndOffset = lineStartOffset + lineText.length;
|
|
100
|
+
const overlapStart = Math.max(rangeStart, lineStartOffset);
|
|
101
|
+
const overlapEnd = Math.min(rangeEnd, lineEndOffset);
|
|
102
|
+
if (overlapEnd > overlapStart) {
|
|
103
|
+
const pointerStart = Math.max(0, overlapStart - lineStartOffset);
|
|
104
|
+
const pointerWidth = Math.max(1, overlapEnd - overlapStart);
|
|
105
|
+
return ' '.repeat(pointerStart) + '^'.repeat(pointerWidth);
|
|
106
|
+
}
|
|
107
|
+
if (lineText.length === 0 && lineNumber >= startLine && lineNumber <= endLine) {
|
|
108
|
+
return '^';
|
|
109
|
+
}
|
|
110
|
+
if (lineNumber === startLine) {
|
|
111
|
+
const caretPos = Math.max(0, rangeStart - lineStartOffset);
|
|
112
|
+
return ' '.repeat(Math.min(caretPos, lineText.length)) + '^';
|
|
113
|
+
}
|
|
114
|
+
return '';
|
|
115
|
+
};
|
|
116
|
+
const buildCodeFrame = (source, start, end, startLine, endLine) => {
|
|
117
|
+
if (!source.length) {
|
|
118
|
+
return '';
|
|
119
|
+
}
|
|
120
|
+
const lines = source.split('\n');
|
|
121
|
+
const offsets = computeLineOffsets(lines);
|
|
122
|
+
const frameStart = Math.max(1, startLine - 1);
|
|
123
|
+
const frameEnd = Math.min(lines.length, endLine + 1);
|
|
124
|
+
const gutterWidth = String(frameEnd).length;
|
|
125
|
+
const frame = [];
|
|
126
|
+
for (let lineNumber = frameStart; lineNumber <= frameEnd; lineNumber++) {
|
|
127
|
+
const lineText = lines[lineNumber - 1] ?? '';
|
|
128
|
+
const gutter = String(lineNumber).padStart(gutterWidth, ' ');
|
|
129
|
+
frame.push(`${gutter} | ${lineText}`);
|
|
130
|
+
const pointer = createPointerLine(lineNumber, lineText, offsets[lineNumber - 1] ?? 0, start, end, startLine, endLine);
|
|
131
|
+
if (pointer) {
|
|
132
|
+
frame.push(`${' '.repeat(gutterWidth)} | ${pointer}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return frame.join('\n');
|
|
136
|
+
};
|
|
137
|
+
const formatTaggedTemplateParserError = (tagName, templates, diagnostics, error, options) => {
|
|
138
|
+
const label = options?.label ?? DEFAULT_LABEL;
|
|
139
|
+
const fallback = `[${label}] ${error.message}`;
|
|
140
|
+
const primaryLabel = error.labels?.[0];
|
|
141
|
+
if (!primaryLabel) {
|
|
142
|
+
return fallback;
|
|
143
|
+
}
|
|
144
|
+
const { source: templateSource, spans } = buildTemplateDisplaySource(templates);
|
|
145
|
+
const combinedSpans = combineExpressionSpans(diagnostics, spans);
|
|
146
|
+
const mapIndex = (value) => {
|
|
147
|
+
const numeric = typeof value === 'number' ? value : 0;
|
|
148
|
+
return mapIndexToTemplate(numeric, combinedSpans, templateSource.length);
|
|
149
|
+
};
|
|
150
|
+
const startIndex = mapIndex(primaryLabel.start);
|
|
151
|
+
let endIndex = mapIndex(primaryLabel.end);
|
|
152
|
+
if (endIndex <= startIndex) {
|
|
153
|
+
endIndex = Math.min(templateSource.length, startIndex + 1);
|
|
154
|
+
}
|
|
155
|
+
const startLocation = getLineAndColumnFromIndex(templateSource, startIndex);
|
|
156
|
+
const endLocation = getLineAndColumnFromIndex(templateSource, Math.max(startIndex, endIndex - 1));
|
|
157
|
+
const codeframe = buildCodeFrame(templateSource, startIndex, endIndex, startLocation.line, endLocation.line);
|
|
158
|
+
let message = `[${label}] ${error.message}`;
|
|
159
|
+
message += `\n--> ${tagName} template:${startLocation.line}:${startLocation.column}`;
|
|
160
|
+
if (primaryLabel.message) {
|
|
161
|
+
message += `\n${primaryLabel.message}`;
|
|
162
|
+
}
|
|
163
|
+
if (codeframe) {
|
|
164
|
+
message += `\n${codeframe}`;
|
|
165
|
+
}
|
|
166
|
+
if (error.helpMessage) {
|
|
167
|
+
message += `\n${error.helpMessage}`;
|
|
168
|
+
}
|
|
169
|
+
return message;
|
|
170
|
+
};
|
|
171
|
+
exports.formatTaggedTemplateParserError = formatTaggedTemplateParserError;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OxcError } from 'oxc-parser';
|
|
2
|
+
export type TemplateExpressionRange = {
|
|
3
|
+
index: number;
|
|
4
|
+
sourceStart: number;
|
|
5
|
+
sourceEnd: number;
|
|
6
|
+
};
|
|
7
|
+
export type TemplateDiagnostics = {
|
|
8
|
+
expressionRanges: TemplateExpressionRange[];
|
|
9
|
+
};
|
|
10
|
+
export type TaggedTemplateFormatOptions = {
|
|
11
|
+
label?: string;
|
|
12
|
+
};
|
|
13
|
+
export declare const formatTaggedTemplateParserError: (tagName: string, templates: TemplateStringsArray, diagnostics: TemplateDiagnostics, error: OxcError, options?: TaggedTemplateFormatOptions) => string;
|
package/dist/cjs/jsx.cjs
CHANGED
|
@@ -4,6 +4,8 @@ exports.jsx = void 0;
|
|
|
4
4
|
const oxc_parser_1 = require("oxc-parser");
|
|
5
5
|
const shared_js_1 = require("./runtime/shared.cjs");
|
|
6
6
|
const property_information_1 = require("property-information");
|
|
7
|
+
const attribute_resolution_js_1 = require("./internal/attribute-resolution.cjs");
|
|
8
|
+
const event_bindings_js_1 = require("./internal/event-bindings.cjs");
|
|
7
9
|
const ensureDomAvailable = () => {
|
|
8
10
|
if (typeof document === 'undefined' || typeof document.createElement !== 'function') {
|
|
9
11
|
throw new Error('The jsx template tag requires a DOM-like environment (document missing).');
|
|
@@ -71,85 +73,6 @@ const shouldAssignProperty = (element, namespace, info) => {
|
|
|
71
73
|
}
|
|
72
74
|
return info.property in element;
|
|
73
75
|
};
|
|
74
|
-
const captureSuffix = 'Capture';
|
|
75
|
-
const stripCaptureSuffix = (rawName) => {
|
|
76
|
-
if (rawName.endsWith(captureSuffix) && rawName.length > captureSuffix.length) {
|
|
77
|
-
return { eventName: rawName.slice(0, -captureSuffix.length), capture: true };
|
|
78
|
-
}
|
|
79
|
-
return { eventName: rawName, capture: false };
|
|
80
|
-
};
|
|
81
|
-
const parseEventPropName = (name) => {
|
|
82
|
-
if (!name.startsWith('on')) {
|
|
83
|
-
return null;
|
|
84
|
-
}
|
|
85
|
-
if (name.startsWith('on:')) {
|
|
86
|
-
const raw = name.slice(3);
|
|
87
|
-
if (!raw) {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
const parsed = stripCaptureSuffix(raw);
|
|
91
|
-
if (!parsed.eventName) {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
return parsed;
|
|
95
|
-
}
|
|
96
|
-
const raw = name.slice(2);
|
|
97
|
-
if (!raw) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
const parsed = stripCaptureSuffix(raw);
|
|
101
|
-
if (!parsed.eventName) {
|
|
102
|
-
return null;
|
|
103
|
-
}
|
|
104
|
-
return {
|
|
105
|
-
eventName: parsed.eventName.toLowerCase(),
|
|
106
|
-
capture: parsed.capture,
|
|
107
|
-
};
|
|
108
|
-
};
|
|
109
|
-
const isEventListenerObject = (value) => {
|
|
110
|
-
if (!value || typeof value !== 'object') {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
return ('handleEvent' in value &&
|
|
114
|
-
typeof value.handleEvent === 'function');
|
|
115
|
-
};
|
|
116
|
-
const isEventHandlerDescriptor = (value) => {
|
|
117
|
-
if (!value || typeof value !== 'object' || !('handler' in value)) {
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
const handler = value.handler;
|
|
121
|
-
if (typeof handler === 'function') {
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
return isEventListenerObject(handler);
|
|
125
|
-
};
|
|
126
|
-
const resolveEventHandlerValue = (value) => {
|
|
127
|
-
if (typeof value === 'function' || isEventListenerObject(value)) {
|
|
128
|
-
return { listener: value };
|
|
129
|
-
}
|
|
130
|
-
if (!isEventHandlerDescriptor(value)) {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
const descriptor = value;
|
|
134
|
-
let options = descriptor.options ? { ...descriptor.options } : undefined;
|
|
135
|
-
const assignOption = (key, optionValue) => {
|
|
136
|
-
if (optionValue === undefined || optionValue === null) {
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
if (!options) {
|
|
140
|
-
options = {};
|
|
141
|
-
}
|
|
142
|
-
options[key] = optionValue;
|
|
143
|
-
};
|
|
144
|
-
assignOption('capture', descriptor.capture);
|
|
145
|
-
assignOption('once', descriptor.once);
|
|
146
|
-
assignOption('passive', descriptor.passive);
|
|
147
|
-
assignOption('signal', descriptor.signal ?? undefined);
|
|
148
|
-
return {
|
|
149
|
-
listener: descriptor.handler,
|
|
150
|
-
options,
|
|
151
|
-
};
|
|
152
|
-
};
|
|
153
76
|
const setDomProp = (element, name, value, namespace) => {
|
|
154
77
|
if (value === null || value === undefined) {
|
|
155
78
|
return;
|
|
@@ -191,9 +114,9 @@ const setDomProp = (element, name, value, namespace) => {
|
|
|
191
114
|
});
|
|
192
115
|
return;
|
|
193
116
|
}
|
|
194
|
-
const eventBinding = parseEventPropName(name);
|
|
117
|
+
const eventBinding = (0, event_bindings_js_1.parseEventPropName)(name);
|
|
195
118
|
if (eventBinding) {
|
|
196
|
-
const handlerValue = resolveEventHandlerValue(value);
|
|
119
|
+
const handlerValue = (0, event_bindings_js_1.resolveEventHandlerValue)(name, value);
|
|
197
120
|
if (handlerValue) {
|
|
198
121
|
let options = handlerValue.options ? { ...handlerValue.options } : undefined;
|
|
199
122
|
if (eventBinding.capture) {
|
|
@@ -288,34 +211,10 @@ const appendChildValue = (parent, value) => {
|
|
|
288
211
|
parent.appendChild(document.createTextNode(String(value)));
|
|
289
212
|
};
|
|
290
213
|
const evaluateExpressionWithNamespace = (expression, ctx, namespace) => (0, shared_js_1.evaluateExpression)(expression, ctx, node => evaluateJsxNode(node, ctx, namespace));
|
|
291
|
-
const resolveAttributes = (
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
const spreadValue = evaluateExpressionWithNamespace(attribute.argument, ctx, namespace);
|
|
296
|
-
if (spreadValue && typeof spreadValue === 'object' && !Array.isArray(spreadValue)) {
|
|
297
|
-
Object.assign(props, spreadValue);
|
|
298
|
-
}
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
const name = (0, shared_js_1.getIdentifierName)(attribute.name);
|
|
302
|
-
if (!attribute.value) {
|
|
303
|
-
props[name] = true;
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
if (attribute.value.type === 'Literal') {
|
|
307
|
-
props[name] = attribute.value.value;
|
|
308
|
-
return;
|
|
309
|
-
}
|
|
310
|
-
if (attribute.value.type === 'JSXExpressionContainer') {
|
|
311
|
-
if (attribute.value.expression.type === 'JSXEmptyExpression') {
|
|
312
|
-
return;
|
|
313
|
-
}
|
|
314
|
-
props[name] = evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
return props;
|
|
318
|
-
};
|
|
214
|
+
const resolveAttributes = (0, attribute_resolution_js_1.createResolveAttributes)({
|
|
215
|
+
getIdentifierName: shared_js_1.getIdentifierName,
|
|
216
|
+
evaluateExpressionWithNamespace,
|
|
217
|
+
});
|
|
319
218
|
const applyDomAttributes = (element, attributes, ctx, namespace) => {
|
|
320
219
|
const props = resolveAttributes(attributes, ctx, namespace);
|
|
321
220
|
Object.entries(props).forEach(([name, value]) => {
|
|
@@ -412,7 +311,7 @@ const jsx = (templates, ...values) => {
|
|
|
412
311
|
const build = (0, shared_js_1.buildTemplate)(templates, values);
|
|
413
312
|
const result = (0, oxc_parser_1.parseSync)('inline.jsx', build.source, shared_js_1.parserOptions);
|
|
414
313
|
if (result.errors.length > 0) {
|
|
415
|
-
throw new Error((0, shared_js_1.
|
|
314
|
+
throw new Error((0, shared_js_1.formatTaggedTemplateParserError)('jsx', templates, build.diagnostics, result.errors[0]));
|
|
416
315
|
}
|
|
417
316
|
const root = (0, shared_js_1.extractRootNode)(result.program);
|
|
418
317
|
const ctx = {
|