typespec-hono 0.5.0 → 0.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.
Files changed (2) hide show
  1. package/dist/src/app.js +29 -7
  2. package/package.json +2 -2
package/dist/src/app.js CHANGED
@@ -600,7 +600,7 @@ securityFor) {
600
600
  "\t\t\t},",
601
601
  "\t\t)",
602
602
  ].join("\n");
603
- return { target, text };
603
+ return { target, text, headOnly };
604
604
  });
605
605
  /**
606
606
  * Every identifier this file names, imported from the module that declares it.
@@ -608,8 +608,21 @@ securityFor) {
608
608
  * **Derived from what the rendered text actually references, not from what was available.** An
609
609
  * unused import fails the lint a generated file has to pass like any other, and a missing one is a
610
610
  * file that does not compile. Both have happened.
611
+ *
612
+ * **The text is TOKENISED and compared by equality, never matched name by name.** It used to build
613
+ * `new RegExp("\\b" + identifier + "\\b")` per name, and that was wrong twice over for any
614
+ * identifier containing a `$`, which TypeSpec permits in a model or operation name: `$` is an
615
+ * anchor in a regular expression, and escaping it alone does not help because `\b` is a WORD
616
+ * boundary and `$` is not a word character, so `\b\$select\b` cannot match either. Measured on
617
+ * `op list$Items(...): Item$Ref[]`, every identifier reported ABSENT, the whole
618
+ * `./schemas.gen.js` import was dropped, and the emitted file failed with four
619
+ * `TS2304: Cannot find name`. No diagnostic anywhere.
620
+ *
621
+ * Splitting on the language's own identifier rule and testing membership has neither failure: no
622
+ * metacharacter can be misread, and `Foo` cannot match `FooExtra`.
611
623
  */
612
624
  const rendered = [...registrations.map((r) => r.text), ...methods, ...aliases].join("\n");
625
+ const mentioned = new Set(rendered.match(/[A-Za-z_$][A-Za-z0-9_$]*/g) ?? []);
613
626
  const referenced = [
614
627
  ...new Set(entries.flatMap((entry) => [
615
628
  entry.names.path,
@@ -620,7 +633,7 @@ securityFor) {
620
633
  entry.names.responses,
621
634
  ].filter((name) => name !== undefined))),
622
635
  ]
623
- .filter((identifier) => new RegExp(`\\b${identifier}\\b`).test(rendered))
636
+ .filter((identifier) => mentioned.has(identifier))
624
637
  .toSorted();
625
638
  const imports = referenced.length === 0
626
639
  ? ""
@@ -670,15 +683,24 @@ securityFor) {
670
683
  // Imported only where a route actually negotiates: an unused import fails the repo's own lint.
671
684
  const negotiates = [...grouped.values()].some((group) => group.length > 1);
672
685
  // Same rule: imported only where a HEAD operation stands alone on its path.
673
- const guardsHead = registrations.some((registration) => registration.text.includes("headOnly,"));
686
+ const guardsHead = registrations.some((registration) => registration.headOnly);
674
687
  /**
675
- * Read from the entries rather than from the rendered text, which the two lines above still do.
688
+ * **Which runtime imports to write is read from the DATA, never from the rendered text.**
676
689
  *
677
- * **Matching a substring of generated output is how this import went missing once already.** It
678
- * tested for `byContentType([`, the emitted call gained arguments before its bracket, the substring
679
- * stopped matching, the import stopped being written, and the emitted module threw
690
+ * Matching a substring of generated output is how one of these went missing already: it tested for
691
+ * `byContentType([`, the emitted call gained arguments before its bracket, the substring stopped
692
+ * matching, the import stopped being written, and the emitted module threw
680
693
  * `ReferenceError: byContentType is not defined` at registration. Nothing in the emitter objected,
681
694
  * because the check was a string about a string.
695
+ *
696
+ * `guardsHead` above had the same shape, and the sibling library had it in a worse place, deciding
697
+ * whether a parameter gets a wire decoder by `declared.startsWith("z.number()")`. A literal union
698
+ * (`@query size: 10 | 25 | 50`) begins with neither prefix, so every conformant caller of that
699
+ * parameter got a 400 that no document comparison could see.
700
+ *
701
+ * The rendered text is still read to decide which VALIDATOR identifiers to import, and that one is
702
+ * safe in a way these are not: an identifier absent from the text is genuinely not needed, so the
703
+ * check cannot be wrong in the direction that breaks a build.
682
704
  */
683
705
  const dispatchesBody = entries.some((entry) => entry.dispatched !== undefined);
684
706
  const runtimeModule = JSON.stringify(emitted.options.runtimeModule);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typespec-hono",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "TypeSpec emitter: generate a Hono server, and the Zod validators it enforces, from an HTTP service definition, agreeing with the OpenAPI document @typespec/openapi3 publishes from the same source.",
5
5
  "keywords": [
6
6
  "cloudflare-workers",
@@ -44,7 +44,7 @@
44
44
  "provenance": true
45
45
  },
46
46
  "dependencies": {
47
- "typespec-http-zod": "^0.5.0"
47
+ "typespec-http-zod": "^0.6.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@hono/zod-openapi": "^1.4.0",