@knighted/jsx 1.5.2 → 1.6.1

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.
Files changed (51) hide show
  1. package/README.md +1 -0
  2. package/dist/cjs/cli/init.cjs +15 -0
  3. package/dist/cjs/debug/diagnostics.cjs +57 -0
  4. package/dist/cjs/debug/diagnostics.d.cts +6 -0
  5. package/dist/cjs/debug/index.cjs +7 -0
  6. package/dist/cjs/debug/index.d.cts +2 -0
  7. package/dist/cjs/internal/attribute-resolution.cjs +22 -4
  8. package/dist/cjs/internal/attribute-resolution.d.cts +5 -0
  9. package/dist/cjs/internal/dev-environment.cjs +41 -0
  10. package/dist/cjs/internal/dev-environment.d.cts +4 -0
  11. package/dist/cjs/internal/event-bindings.cjs +11 -2
  12. package/dist/cjs/internal/event-bindings.d.cts +5 -1
  13. package/dist/cjs/internal/template-diagnostics.cjs +171 -0
  14. package/dist/cjs/internal/template-diagnostics.d.cts +13 -0
  15. package/dist/cjs/jsx.cjs +2 -2
  16. package/dist/cjs/loader/jsx.cjs +72 -20
  17. package/dist/cjs/loader/jsx.d.cts +6 -1
  18. package/dist/cjs/node/debug/index.cjs +6 -0
  19. package/dist/cjs/node/debug/index.d.cts +2 -0
  20. package/dist/cjs/react/react-jsx.cjs +1 -1
  21. package/dist/cjs/runtime/shared.cjs +41 -22
  22. package/dist/cjs/runtime/shared.d.cts +5 -2
  23. package/dist/cli/init.js +17 -0
  24. package/dist/debug/diagnostics.d.ts +6 -0
  25. package/dist/debug/diagnostics.js +52 -0
  26. package/dist/debug/index.d.ts +2 -0
  27. package/dist/debug/index.js +3 -0
  28. package/dist/internal/attribute-resolution.d.ts +5 -0
  29. package/dist/internal/attribute-resolution.js +20 -3
  30. package/dist/internal/dev-environment.d.ts +4 -0
  31. package/dist/internal/dev-environment.js +34 -0
  32. package/dist/internal/event-bindings.d.ts +5 -1
  33. package/dist/internal/event-bindings.js +9 -1
  34. package/dist/internal/template-diagnostics.d.ts +13 -0
  35. package/dist/internal/template-diagnostics.js +167 -0
  36. package/dist/jsx.js +3 -3
  37. package/dist/lite/debug/diagnostics.js +1 -0
  38. package/dist/lite/debug/index.js +8 -0
  39. package/dist/lite/index.js +8 -4
  40. package/dist/lite/node/debug/index.js +8 -0
  41. package/dist/lite/node/index.js +8 -4
  42. package/dist/lite/node/react/index.js +7 -3
  43. package/dist/lite/react/index.js +7 -3
  44. package/dist/loader/jsx.d.ts +6 -1
  45. package/dist/loader/jsx.js +72 -20
  46. package/dist/node/debug/index.d.ts +2 -0
  47. package/dist/node/debug/index.js +6 -0
  48. package/dist/react/react-jsx.js +2 -2
  49. package/dist/runtime/shared.d.ts +5 -2
  50. package/dist/runtime/shared.js +39 -21
  51. package/package.json +39 -7
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.default = jsxLoader;
7
7
  const magic_string_1 = __importDefault(require("magic-string"));
8
8
  const oxc_parser_1 = require("oxc-parser");
9
+ const template_diagnostics_js_1 = require("../internal/template-diagnostics.cjs");
9
10
  const createPlaceholderMap = (placeholders) => new Map(placeholders.map(entry => [entry.marker, entry.code]));
10
11
  class ReactTemplateBuilder {
11
12
  placeholderMap;
@@ -383,10 +384,12 @@ const renderTemplateWithSlots = (source, slots) => {
383
384
  output += escapeTemplateChunk(source.slice(cursor));
384
385
  return { code: output, changed: slots.length > 0 };
385
386
  };
386
- const transformTemplateLiteral = (templateSource, resourcePath) => {
387
+ const transformTemplateLiteral = (templateSource, resourcePath, tagName, templates, diagnostics) => {
387
388
  const result = (0, oxc_parser_1.parseSync)(`${resourcePath}?jsx-template`, templateSource, TEMPLATE_PARSER_OPTIONS);
388
389
  if (result.errors.length > 0) {
389
- throw new Error(formatParserError(result.errors[0]));
390
+ throw new Error((0, template_diagnostics_js_1.formatTaggedTemplateParserError)(tagName, templates, diagnostics, result.errors[0], {
391
+ label: 'jsx-loader',
392
+ }));
390
393
  }
391
394
  const slots = collectSlots(result.program, templateSource);
392
395
  return renderTemplateWithSlots(templateSource, slots);
@@ -455,6 +458,25 @@ const normalizeJsxTextSegments = (value, placeholders) => {
455
458
  return segments;
456
459
  };
457
460
  const TAG_PLACEHOLDER_PREFIX = '__JSX_LOADER_TAG_EXPR_';
461
+ const materializeTemplateStrings = (quasis) => {
462
+ const cooked = [];
463
+ const raw = [];
464
+ quasis.forEach(quasi => {
465
+ const value = quasi.value;
466
+ const cookedChunk = typeof value.cooked === 'string' ? value.cooked : (value.raw ?? '');
467
+ const rawChunk = typeof value.raw === 'string' ? value.raw : cookedChunk;
468
+ cooked.push(cookedChunk);
469
+ raw.push(rawChunk);
470
+ });
471
+ const templates = cooked;
472
+ Object.defineProperty(templates, 'raw', {
473
+ value: raw,
474
+ writable: false,
475
+ configurable: false,
476
+ enumerable: false,
477
+ });
478
+ return templates;
479
+ };
458
480
  const buildTemplateSource = (quasis, expressions, source, tag) => {
459
481
  const placeholderMap = new Map();
460
482
  const tagPlaceholderMap = new Map();
@@ -462,6 +484,7 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
462
484
  let placeholderIndex = 0;
463
485
  let trimStartNext = 0;
464
486
  let mutated = false;
487
+ const expressionRanges = [];
465
488
  const registerMarker = (code, isTag) => {
466
489
  if (isTag) {
467
490
  const existing = tagPlaceholderMap.get(code);
@@ -477,6 +500,12 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
477
500
  placeholderMap.set(marker, code);
478
501
  return marker;
479
502
  };
503
+ const appendInsertion = (expressionIndex, insertion) => {
504
+ const start = template.length;
505
+ template += insertion;
506
+ const end = template.length;
507
+ expressionRanges.push({ index: expressionIndex, sourceStart: start, sourceEnd: end });
508
+ };
480
509
  quasis.forEach((quasi, index) => {
481
510
  let chunk = quasi.value.cooked;
482
511
  if (typeof chunk !== 'string') {
@@ -494,6 +523,7 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
494
523
  if (!expression) {
495
524
  return;
496
525
  }
526
+ const expressionIndex = index;
497
527
  const start = expression.start ?? null;
498
528
  const end = expression.end ?? null;
499
529
  if (start === null || end === null) {
@@ -509,7 +539,8 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
509
539
  const code = source.slice(start, end);
510
540
  const marker = registerMarker(code, context.type === 'tag');
511
541
  const appendMarker = (wrapper) => {
512
- template += wrapper ? wrapper(marker) : marker;
542
+ const insertion = wrapper ? wrapper(marker) : marker;
543
+ appendInsertion(expressionIndex, insertion);
513
544
  };
514
545
  switch (context.type) {
515
546
  case 'tag':
@@ -551,15 +582,22 @@ const buildTemplateSource = (quasis, expressions, source, tag) => {
551
582
  marker,
552
583
  code,
553
584
  })),
585
+ diagnostics: { expressionRanges },
554
586
  };
555
587
  };
556
588
  const restoreTemplatePlaceholders = (code, placeholders) => placeholders.reduce((result, placeholder) => {
557
589
  return result.split(placeholder.marker).join(`\${${placeholder.code}}`);
558
590
  }, code);
559
- const compileReactTemplate = (templateSource, placeholders, resourcePath) => {
591
+ const createInlineSourceMapComment = (map) => {
592
+ const payload = Buffer.from(JSON.stringify(map), 'utf8').toString('base64');
593
+ return `//# sourceMappingURL=data:application/json;charset=utf-8;base64,${payload}`;
594
+ };
595
+ const compileReactTemplate = (templateSource, placeholders, resourcePath, tagName, templates, diagnostics) => {
560
596
  const parsed = (0, oxc_parser_1.parseSync)(`${resourcePath}?jsx-react-template`, templateSource, TEMPLATE_PARSER_OPTIONS);
561
597
  if (parsed.errors.length > 0) {
562
- throw new Error(formatParserError(parsed.errors[0]));
598
+ throw new Error((0, template_diagnostics_js_1.formatTaggedTemplateParserError)(tagName, templates, diagnostics, parsed.errors[0], {
599
+ label: 'jsx-loader',
600
+ }));
563
601
  }
564
602
  const root = extractJsxRoot(parsed.program);
565
603
  const builder = new ReactTemplateBuilder(placeholders);
@@ -577,7 +615,7 @@ const isLoaderPlaceholderIdentifier = (node) => {
577
615
  return (node.name.startsWith(TEMPLATE_EXPR_PLACEHOLDER_PREFIX) ||
578
616
  node.name.startsWith(TAG_PLACEHOLDER_PREFIX));
579
617
  };
580
- const transformSource = (source, config) => {
618
+ const transformSource = (source, config, options) => {
581
619
  const ast = (0, oxc_parser_1.parseSync)(config.resourcePath, source, MODULE_PARSER_OPTIONS);
582
620
  if (ast.errors.length > 0) {
583
621
  throw new Error(formatParserError(ast.errors[0]));
@@ -590,7 +628,7 @@ const transformSource = (source, config) => {
590
628
  }
591
629
  });
592
630
  if (!taggedTemplates.length) {
593
- return { code: source, helpers: [] };
631
+ return { code: source, mutated: false };
594
632
  }
595
633
  const magic = new magic_string_1.default(source);
596
634
  let mutated = false;
@@ -602,8 +640,9 @@ const transformSource = (source, config) => {
602
640
  const mode = config.tagModes.get(tagName) ?? DEFAULT_MODE;
603
641
  const quasi = node.quasi;
604
642
  const templateSource = buildTemplateSource(quasi.quasis, quasi.expressions, source, tagName);
643
+ const templateStrings = materializeTemplateStrings(quasi.quasis);
605
644
  if (mode === 'runtime') {
606
- const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath);
645
+ const { code, changed } = transformTemplateLiteral(templateSource.source, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
607
646
  const restored = restoreTemplatePlaceholders(code, templateSource.placeholders);
608
647
  const templateChanged = changed || templateSource.mutated;
609
648
  if (!templateChanged) {
@@ -616,7 +655,7 @@ const transformSource = (source, config) => {
616
655
  return;
617
656
  }
618
657
  if (mode === 'react') {
619
- const compiled = compileReactTemplate(templateSource.source, templateSource.placeholders, config.resourcePath);
658
+ const compiled = compileReactTemplate(templateSource.source, templateSource.placeholders, config.resourcePath, tagName, templateStrings, templateSource.diagnostics);
620
659
  helperKinds.add('react');
621
660
  magic.overwrite(node.start, node.end, compiled);
622
661
  mutated = true;
@@ -627,11 +666,26 @@ const transformSource = (source, config) => {
627
666
  // Modes are validated during option parsing; this fallback guards future extensions.
628
667
  throw new Error(`[jsx-loader] Transformation mode "${mode}" not implemented yet for tag "${tagName}".`);
629
668
  });
669
+ const helperSource = Array.from(helperKinds)
670
+ .map(kind => HELPER_SNIPPETS[kind])
671
+ .filter(Boolean)
672
+ .join('\n');
673
+ if (helperSource) {
674
+ magic.append(`\n${helperSource}`);
675
+ mutated = true;
676
+ }
677
+ const code = mutated ? magic.toString() : source;
678
+ const map = options?.sourceMap && mutated
679
+ ? magic.generateMap({
680
+ hires: true,
681
+ source: config.resourcePath,
682
+ includeContent: true,
683
+ })
684
+ : undefined;
630
685
  return {
631
- code: mutated ? magic.toString() : source,
632
- helpers: Array.from(helperKinds)
633
- .map(kind => HELPER_SNIPPETS[kind])
634
- .filter(Boolean),
686
+ code,
687
+ map,
688
+ mutated,
635
689
  };
636
690
  };
637
691
  function jsxLoader(input) {
@@ -668,16 +722,14 @@ function jsxLoader(input) {
668
722
  }
669
723
  });
670
724
  const source = typeof input === 'string' ? input : input.toString('utf8');
671
- const { code, helpers } = transformSource(source, {
725
+ const enableSourceMap = options.sourceMap === true;
726
+ const { code, map } = transformSource(source, {
672
727
  resourcePath: this.resourcePath,
673
728
  tags,
674
729
  tagModes,
675
- });
676
- if (helpers.length) {
677
- callback(null, `${code}\n${helpers.join('\n')}`);
678
- return;
679
- }
680
- callback(null, code);
730
+ }, { sourceMap: enableSourceMap });
731
+ const output = map && enableSourceMap ? `${code}\n${createInlineSourceMapComment(map)}` : code;
732
+ callback(null, output, map);
681
733
  }
682
734
  catch (error) {
683
735
  callback(error);
@@ -1,4 +1,5 @@
1
- type LoaderCallback = (err: Error | null, content?: string) => void;
1
+ import { type SourceMap } from 'magic-string';
2
+ type LoaderCallback = (error: Error | null, content?: string, map?: SourceMap | null) => void;
2
3
  type LoaderContext<TOptions> = {
3
4
  resourcePath: string;
4
5
  async(): LoaderCallback;
@@ -24,6 +25,10 @@ type LoaderOptions = {
24
25
  * Optional per-tag override of the transformation mode. Keys map to tag names.
25
26
  */
26
27
  tagModes?: Record<string, LoaderMode | undefined>;
28
+ /**
29
+ * When true, generate inline source maps for mutated files.
30
+ */
31
+ sourceMap?: boolean;
27
32
  };
28
33
  export default function jsxLoader(this: LoaderContext<LoaderOptions>, input: string | Buffer): void;
29
34
  export {};
@@ -0,0 +1,6 @@
1
+ import { enableJsxDebugDiagnostics } from '../../debug/diagnostics.cjs';
2
+ import { ensureNodeDom } from '../bootstrap.cjs';
3
+ import { jsx as baseJsx } from '../../jsx.cjs';
4
+ enableJsxDebugDiagnostics({ mode: 'always' });
5
+ ensureNodeDom();
6
+ export const jsx = baseJsx;
@@ -0,0 +1,2 @@
1
+ export declare const jsx: (templates: TemplateStringsArray, ...values: unknown[]) => import("../../jsx.cjs").JsxRenderable;
2
+ export type { JsxRenderable, JsxComponent } from '../../jsx.cjs';
@@ -127,7 +127,7 @@ const reactJsx = (templates, ...values) => {
127
127
  const build = (0, shared_js_1.buildTemplate)(templates, values);
128
128
  const result = (0, oxc_parser_1.parseSync)('inline.jsx', build.source, shared_js_1.parserOptions);
129
129
  if (result.errors.length > 0) {
130
- throw new Error((0, shared_js_1.formatParserError)(result.errors[0]));
130
+ throw new Error((0, shared_js_1.formatTaggedTemplateParserError)('reactJsx', templates, build.diagnostics, result.errors[0]));
131
131
  }
132
132
  const root = (0, shared_js_1.extractRootNode)(result.program);
133
133
  const ctx = {
@@ -1,31 +1,34 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.buildTemplate = exports.ensureBinding = exports.sanitizeIdentifier = exports.evaluateExpression = exports.collectPlaceholderNames = exports.normalizeJsxTextSegments = exports.walkAst = exports.getIdentifierName = exports.extractRootNode = exports.formatParserError = exports.parserOptions = exports.placeholderPattern = exports.PLACEHOLDER_PREFIX = void 0;
3
+ exports.buildTemplate = exports.ensureBinding = exports.sanitizeIdentifier = exports.evaluateExpression = exports.collectPlaceholderNames = exports.normalizeJsxTextSegments = exports.walkAst = exports.getIdentifierName = exports.extractRootNode = exports.parserOptions = exports.formatParserError = exports.placeholderPattern = exports.PLACEHOLDER_PREFIX = exports.formatTaggedTemplateParserError = void 0;
4
+ var template_diagnostics_js_1 = require("../internal/template-diagnostics.cjs");
5
+ Object.defineProperty(exports, "formatTaggedTemplateParserError", { enumerable: true, get: function () { return template_diagnostics_js_1.formatTaggedTemplateParserError; } });
4
6
  const OPEN_TAG_RE = /<\s*$/;
5
7
  const CLOSE_TAG_RE = /<\/\s*$/;
6
8
  exports.PLACEHOLDER_PREFIX = '__KX_EXPR__';
7
9
  exports.placeholderPattern = new RegExp(`${exports.PLACEHOLDER_PREFIX}\\d+_\\d+__`, 'g');
8
10
  let invocationCounter = 0;
9
- exports.parserOptions = {
10
- lang: 'jsx',
11
- sourceType: 'module',
12
- range: true,
13
- preserveParens: true,
14
- };
15
11
  const formatParserError = (error) => {
16
12
  let message = `[oxc-parser] ${error.message}`;
17
- if (error.labels?.length) {
18
- const label = error.labels[0];
19
- if (label.message) {
20
- message += `\n${label.message}`;
21
- }
13
+ const primaryLabel = error.labels?.[0];
14
+ if (primaryLabel?.message) {
15
+ message += `\n${primaryLabel.message}`;
22
16
  }
23
17
  if (error.codeframe) {
24
18
  message += `\n${error.codeframe}`;
25
19
  }
20
+ if (error.helpMessage) {
21
+ message += `\n${error.helpMessage}`;
22
+ }
26
23
  return message;
27
24
  };
28
25
  exports.formatParserError = formatParserError;
26
+ exports.parserOptions = {
27
+ lang: 'jsx',
28
+ sourceType: 'module',
29
+ range: true,
30
+ preserveParens: true,
31
+ };
29
32
  const extractRootNode = (program) => {
30
33
  for (const statement of program.body) {
31
34
  if (statement.type === 'ExpressionStatement') {
@@ -183,24 +186,40 @@ const buildTemplate = (strings, values) => {
183
186
  let source = raw[0] ?? '';
184
187
  const templateId = invocationCounter++;
185
188
  let placeholderIndex = 0;
189
+ const expressionRanges = [];
186
190
  for (let idx = 0; idx < values.length; idx++) {
187
191
  const chunk = raw[idx] ?? '';
188
192
  const nextChunk = raw[idx + 1] ?? '';
189
193
  const value = values[idx];
190
194
  const isTagNamePosition = OPEN_TAG_RE.test(chunk) || CLOSE_TAG_RE.test(chunk);
195
+ let insertion;
191
196
  if (isTagNamePosition && typeof value === 'function') {
192
197
  const binding = (0, exports.ensureBinding)(value, bindings, bindingLookup);
193
- source += binding.name + nextChunk;
194
- continue;
198
+ insertion = binding.name;
195
199
  }
196
- if (isTagNamePosition && typeof value === 'string') {
197
- source += value + nextChunk;
198
- continue;
200
+ else if (isTagNamePosition && typeof value === 'string') {
201
+ insertion = value;
199
202
  }
200
- const placeholder = `${exports.PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
201
- placeholders.set(placeholder, value);
202
- source += placeholder + nextChunk;
203
- }
204
- return { source, placeholders, bindings };
203
+ else {
204
+ const placeholder = `${exports.PLACEHOLDER_PREFIX}${templateId}_${placeholderIndex++}__`;
205
+ placeholders.set(placeholder, value);
206
+ insertion = placeholder;
207
+ }
208
+ const sourceStart = source.length;
209
+ source += insertion;
210
+ const sourceEnd = source.length;
211
+ expressionRanges.push({
212
+ index: idx,
213
+ sourceStart,
214
+ sourceEnd,
215
+ });
216
+ source += nextChunk;
217
+ }
218
+ return {
219
+ source,
220
+ placeholders,
221
+ bindings,
222
+ diagnostics: { expressionRanges },
223
+ };
205
224
  };
206
225
  exports.buildTemplate = buildTemplate;
@@ -1,7 +1,11 @@
1
1
  import type { Expression, JSXElement, JSXFragment, JSXIdentifier, JSXMemberExpression, JSXNamespacedName, Program } from '@oxc-project/types';
2
2
  import type { OxcError, ParserOptions } from 'oxc-parser';
3
+ import type { TemplateDiagnostics } from '../internal/template-diagnostics.cjs';
4
+ export { formatTaggedTemplateParserError } from '../internal/template-diagnostics.cjs';
5
+ export type { TemplateDiagnostics, TemplateExpressionRange, } from '../internal/template-diagnostics.cjs';
3
6
  export declare const PLACEHOLDER_PREFIX = "__KX_EXPR__";
4
7
  export declare const placeholderPattern: RegExp;
8
+ export declare const formatParserError: (error: OxcError) => string;
5
9
  type AnyTemplateFunction = (...args: any[]) => unknown;
6
10
  type AnyTemplateConstructor = abstract new (...args: any[]) => unknown;
7
11
  export type TemplateComponent = (AnyTemplateFunction | AnyTemplateConstructor) & {
@@ -16,6 +20,7 @@ export type TemplateBuildResult<TComponent extends TemplateComponent> = {
16
20
  source: string;
17
21
  placeholders: Map<string, unknown>;
18
22
  bindings: BindingEntry<TComponent>[];
23
+ diagnostics: TemplateDiagnostics;
19
24
  };
20
25
  export type TemplateContext<TComponent extends TemplateComponent> = {
21
26
  source: string;
@@ -23,7 +28,6 @@ export type TemplateContext<TComponent extends TemplateComponent> = {
23
28
  components: Map<string, TComponent>;
24
29
  };
25
30
  export declare const parserOptions: ParserOptions;
26
- export declare const formatParserError: (error: OxcError) => string;
27
31
  export declare const extractRootNode: (program: Program) => JSXElement | JSXFragment;
28
32
  export declare const getIdentifierName: (identifier: JSXIdentifier | JSXNamespacedName | JSXMemberExpression) => string;
29
33
  type AnyOxcNode = {
@@ -37,4 +41,3 @@ export declare const evaluateExpression: <TComponent extends TemplateComponent>(
37
41
  export declare const sanitizeIdentifier: (value: string) => string;
38
42
  export declare const ensureBinding: <TComponent extends TemplateComponent>(value: TComponent, bindings: BindingEntry<TComponent>[], bindingLookup: Map<TComponent, BindingEntry<TComponent>>) => BindingEntry<TComponent>;
39
43
  export declare const buildTemplate: <TComponent extends TemplateComponent>(strings: TemplateStringsArray, values: unknown[]) => TemplateBuildResult<TComponent>;
40
- export {};
package/dist/cli/init.js CHANGED
@@ -143,6 +143,23 @@ function parsePackageName(spec) {
143
143
  return { name, version };
144
144
  }
145
145
  function readLocalOxcParserVersion(resolver = CLI_REQUIRE) {
146
+ try {
147
+ const jsxPkgPath = resolver.resolve("@knighted/jsx/package.json");
148
+ const jsxRoot = path.dirname(jsxPkgPath);
149
+ const localParserPkg = path.join(
150
+ jsxRoot,
151
+ "node_modules",
152
+ "oxc-parser",
153
+ "package.json"
154
+ );
155
+ if (fs.existsSync(localParserPkg)) {
156
+ const pkgJson = JSON.parse(fs.readFileSync(localParserPkg, "utf8"));
157
+ if (pkgJson && typeof pkgJson.version === "string") {
158
+ return pkgJson.version;
159
+ }
160
+ }
161
+ } catch {
162
+ }
146
163
  try {
147
164
  const pkg = resolver("oxc-parser/package.json");
148
165
  if (pkg && typeof pkg.version === "string") {
@@ -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,52 @@
1
+ import { setAttributeDiagnosticsHooks, } from '../internal/attribute-resolution.js';
2
+ import { setEventDiagnosticsHooks, } from '../internal/event-bindings.js';
3
+ import { createDevError, describeValue, emitDevWarning, isDevEnvironment, } from '../internal/dev-environment.js';
4
+ const isAsciiLowercase = (char) => char >= 'a' && char <= 'z';
5
+ let diagnosticsMode = 'env';
6
+ const shouldRunDiagnostics = () => diagnosticsMode === 'always' || (diagnosticsMode === 'env' && isDevEnvironment());
7
+ const shouldForceWarnings = () => diagnosticsMode === 'always';
8
+ const attributeDiagnostics = {
9
+ warnLowercaseEventProp(name) {
10
+ if (!shouldRunDiagnostics()) {
11
+ return;
12
+ }
13
+ if (!name.startsWith('on') || name.startsWith('on:') || name.length < 3) {
14
+ return;
15
+ }
16
+ const indicator = name[2] ?? '';
17
+ if (!isAsciiLowercase(indicator)) {
18
+ return;
19
+ }
20
+ const suggestion = `${name.slice(0, 2)}${indicator.toUpperCase()}${name.slice(3)}`;
21
+ emitDevWarning(`Use camelCase DOM event props when targeting runtime jsx templates. Received "${name}"; did you mean "${suggestion}"?`, shouldForceWarnings());
22
+ },
23
+ ensureValidDangerouslySetInnerHTML(value) {
24
+ if (!shouldRunDiagnostics()) {
25
+ return;
26
+ }
27
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
28
+ throw createDevError('dangerouslySetInnerHTML expects an object with a string __html field.');
29
+ }
30
+ const html = value.__html;
31
+ if (typeof html !== 'string') {
32
+ throw createDevError(`dangerouslySetInnerHTML.__html must be a string but received ${describeValue(html)}.`);
33
+ }
34
+ },
35
+ };
36
+ const eventDiagnostics = {
37
+ onInvalidHandler(propName, value) {
38
+ if (!shouldRunDiagnostics()) {
39
+ return;
40
+ }
41
+ throw createDevError(`The "${propName}" prop expects a function, EventListenerObject, or descriptor ({ handler }) but received ${describeValue(value)}.`);
42
+ },
43
+ };
44
+ export const enableJsxDebugDiagnostics = (options) => {
45
+ diagnosticsMode = options?.mode ?? 'env';
46
+ setAttributeDiagnosticsHooks(attributeDiagnostics);
47
+ setEventDiagnosticsHooks(eventDiagnostics);
48
+ };
49
+ export const disableJsxDebugDiagnostics = () => {
50
+ setAttributeDiagnosticsHooks(null);
51
+ setEventDiagnosticsHooks(null);
52
+ };
@@ -0,0 +1,2 @@
1
+ export { jsx } from '../jsx.js';
2
+ export type { JsxRenderable, JsxComponent } from '../jsx.js';
@@ -0,0 +1,3 @@
1
+ import { enableJsxDebugDiagnostics } from './diagnostics.js';
2
+ enableJsxDebugDiagnostics({ mode: 'always' });
3
+ export { jsx } from '../jsx.js';
@@ -1,5 +1,10 @@
1
1
  import type { Expression, JSXAttribute, JSXElement, JSXFragment, JSXSpreadAttribute } from '@oxc-project/types';
2
2
  import type { TemplateComponent, TemplateContext } from '../runtime/shared.js';
3
+ export type AttributeDiagnosticsHooks = {
4
+ warnLowercaseEventProp?: (name: string) => void;
5
+ ensureValidDangerouslySetInnerHTML?: (value: unknown) => void;
6
+ };
7
+ export declare const setAttributeDiagnosticsHooks: (hooks: AttributeDiagnosticsHooks | null) => void;
3
8
  export type Namespace = 'svg' | null;
4
9
  export type EvaluateExpressionWithNamespace<TComponent extends TemplateComponent> = (expression: Expression | JSXElement | JSXFragment, ctx: TemplateContext<TComponent>, namespace: Namespace) => unknown;
5
10
  export type ResolveAttributesDependencies<TComponent extends TemplateComponent> = {
@@ -1,7 +1,23 @@
1
+ let attributeDiagnostics = null;
2
+ export const setAttributeDiagnosticsHooks = (hooks) => {
3
+ attributeDiagnostics = hooks;
4
+ };
5
+ const warnLowercaseEventProp = (name) => {
6
+ attributeDiagnostics?.warnLowercaseEventProp?.(name);
7
+ };
8
+ const ensureValidDangerouslySetInnerHTML = (value) => {
9
+ attributeDiagnostics?.ensureValidDangerouslySetInnerHTML?.(value);
10
+ };
1
11
  export const createResolveAttributes = (deps) => {
2
12
  const { getIdentifierName, evaluateExpressionWithNamespace } = deps;
3
13
  return (attributes, ctx, namespace) => {
4
14
  const props = {};
15
+ const assignProp = (propName, propValue) => {
16
+ if (propName === 'dangerouslySetInnerHTML') {
17
+ ensureValidDangerouslySetInnerHTML(propValue);
18
+ }
19
+ props[propName] = propValue;
20
+ };
5
21
  attributes.forEach(attribute => {
6
22
  if (attribute.type === 'JSXSpreadAttribute') {
7
23
  const spreadValue = evaluateExpressionWithNamespace(attribute.argument, ctx, namespace);
@@ -13,19 +29,20 @@ export const createResolveAttributes = (deps) => {
13
29
  return;
14
30
  }
15
31
  const name = getIdentifierName(attribute.name);
32
+ warnLowercaseEventProp(name);
16
33
  if (!attribute.value) {
17
- props[name] = true;
34
+ assignProp(name, true);
18
35
  return;
19
36
  }
20
37
  if (attribute.value.type === 'Literal') {
21
- props[name] = attribute.value.value;
38
+ assignProp(name, attribute.value.value);
22
39
  return;
23
40
  }
24
41
  if (attribute.value.type === 'JSXExpressionContainer') {
25
42
  if (attribute.value.expression.type === 'JSXEmptyExpression') {
26
43
  return;
27
44
  }
28
- props[name] = evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace);
45
+ assignProp(name, evaluateExpressionWithNamespace(attribute.value.expression, ctx, namespace));
29
46
  }
30
47
  });
31
48
  return props;
@@ -0,0 +1,4 @@
1
+ export declare const isDevEnvironment: () => boolean;
2
+ export declare const describeValue: (value: unknown) => string;
3
+ export declare const createDevError: (message: string) => Error;
4
+ export declare const emitDevWarning: (message: string, force?: boolean) => void;
@@ -0,0 +1,34 @@
1
+ const DEV_PREFIX = '@knighted/jsx';
2
+ const formatDevMessage = (message) => `${DEV_PREFIX}: ${message}`;
3
+ export const isDevEnvironment = () => typeof process !== 'undefined' && process.env?.KNIGHTED_JSX_DEBUG === '1';
4
+ export const describeValue = (value) => {
5
+ if (value === null) {
6
+ return 'null';
7
+ }
8
+ if (value === undefined) {
9
+ return 'undefined';
10
+ }
11
+ if (typeof value === 'function') {
12
+ return value.name ? `function ${value.name}` : 'function';
13
+ }
14
+ if (Array.isArray(value)) {
15
+ return 'array';
16
+ }
17
+ if (typeof value === 'object') {
18
+ const ctor = value.constructor;
19
+ if (ctor && typeof ctor.name === 'string' && ctor.name && ctor.name !== 'Object') {
20
+ return `${ctor.name} instance`;
21
+ }
22
+ return 'object';
23
+ }
24
+ return typeof value;
25
+ };
26
+ export const createDevError = (message) => new Error(formatDevMessage(message));
27
+ export const emitDevWarning = (message, force = false) => {
28
+ if (!force && !isDevEnvironment()) {
29
+ return;
30
+ }
31
+ if (typeof console !== 'undefined' && typeof console.warn === 'function') {
32
+ console.warn(formatDevMessage(message));
33
+ }
34
+ };
@@ -1,3 +1,7 @@
1
+ export type EventDiagnosticsHooks = {
2
+ onInvalidHandler?: (propName: string, value: unknown) => void;
3
+ };
4
+ export declare const setEventDiagnosticsHooks: (hooks: EventDiagnosticsHooks | null) => void;
1
5
  export type ParsedEventBinding = {
2
6
  eventName: string;
3
7
  capture: boolean;
@@ -15,4 +19,4 @@ export type ResolvedEventHandler = {
15
19
  listener: EventListenerOrEventListenerObject;
16
20
  options?: AddEventListenerOptions;
17
21
  };
18
- export declare const resolveEventHandlerValue: (value: unknown) => ResolvedEventHandler | null;
22
+ export declare const resolveEventHandlerValue: (propName: string, value: unknown) => ResolvedEventHandler | null;
@@ -1,3 +1,7 @@
1
+ let eventDiagnostics = null;
2
+ export const setEventDiagnosticsHooks = (hooks) => {
3
+ eventDiagnostics = hooks;
4
+ };
1
5
  const captureSuffix = 'Capture';
2
6
  const stripCaptureSuffix = (rawName) => {
3
7
  if (rawName.endsWith(captureSuffix)) {
@@ -50,11 +54,15 @@ const isEventHandlerDescriptor = (value) => {
50
54
  }
51
55
  return isEventListenerObject(handler);
52
56
  };
53
- export const resolveEventHandlerValue = (value) => {
57
+ const throwInvalidHandlerError = (propName, value) => {
58
+ eventDiagnostics?.onInvalidHandler?.(propName, value);
59
+ };
60
+ export const resolveEventHandlerValue = (propName, value) => {
54
61
  if (typeof value === 'function' || isEventListenerObject(value)) {
55
62
  return { listener: value };
56
63
  }
57
64
  if (!isEventHandlerDescriptor(value)) {
65
+ throwInvalidHandlerError(propName, value);
58
66
  return null;
59
67
  }
60
68
  const descriptor = value;
@@ -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;