typia 14.0.6 → 15.0.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 (72) hide show
  1. package/README.md +3 -0
  2. package/lib/internal/_createLlmEvaluation.d.ts +1 -0
  3. package/lib/internal/_createLlmEvaluation.js +486 -0
  4. package/lib/internal/_createLlmEvaluation.js.map +1 -0
  5. package/lib/internal/_createLlmEvaluation.mjs +382 -0
  6. package/lib/internal/_createLlmEvaluation.mjs.map +1 -0
  7. package/lib/internal/_stringLengthGte.d.ts +1 -0
  8. package/lib/internal/_stringLengthGte.js +14 -0
  9. package/lib/internal/_stringLengthGte.js.map +1 -0
  10. package/lib/internal/_stringLengthGte.mjs +11 -0
  11. package/lib/internal/_stringLengthGte.mjs.map +1 -0
  12. package/lib/internal/_stringLengthLte.d.ts +1 -0
  13. package/lib/internal/_stringLengthLte.js +14 -0
  14. package/lib/internal/_stringLengthLte.js.map +1 -0
  15. package/lib/internal/_stringLengthLte.mjs +11 -0
  16. package/lib/internal/_stringLengthLte.mjs.map +1 -0
  17. package/lib/llm.d.ts +54 -1
  18. package/lib/llm.js +5 -0
  19. package/lib/llm.js.map +1 -1
  20. package/lib/llm.mjs +6 -1
  21. package/lib/llm.mjs.map +1 -1
  22. package/lib/re-exports.d.ts +1 -1
  23. package/lib/re-exports.js.map +1 -1
  24. package/native/cmd/ttsc-typia/build.go +2 -0
  25. package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +1 -1
  26. package/native/cmd/ttsc-typia/llm_evaluation_accepts_property_probability_test.go +27 -0
  27. package/native/cmd/ttsc-typia/llm_evaluation_accepts_unselected_source_probability_test.go +26 -0
  28. package/native/cmd/ttsc-typia/llm_evaluation_complete_object_shape_test.go +96 -0
  29. package/native/cmd/ttsc-typia/llm_evaluation_ignores_declaration_probability_without_call_test.go +18 -0
  30. package/native/cmd/ttsc-typia/llm_evaluation_plan_emission_transform_test.go +90 -0
  31. package/native/cmd/ttsc-typia/llm_evaluation_program_scan_dependencies_test.go +69 -0
  32. package/native/cmd/ttsc-typia/llm_evaluation_rejects_alias_comment_test.go +44 -0
  33. package/native/cmd/ttsc-typia/llm_evaluation_rejects_alias_refinement_test.go +45 -0
  34. package/native/cmd/ttsc-typia/llm_evaluation_rejects_computed_method_probability_test.go +30 -0
  35. package/native/cmd/ttsc-typia/llm_evaluation_rejects_imported_declaration_probability_test.go +48 -0
  36. package/native/cmd/ttsc-typia/llm_evaluation_rejects_invalid_probability_test.go +199 -0
  37. package/native/cmd/ttsc-typia/llm_evaluation_rejects_refinements_test.go +94 -0
  38. package/native/cmd/ttsc-typia/llm_evaluation_rejects_undecidable_types_test.go +232 -0
  39. package/native/cmd/ttsc-typia/metadata_type_tag_sole_malformed_diagnostic_test.go +94 -0
  40. package/native/cmd/ttsc-typia/template_literal_type_tags_transform_test.go +3 -2
  41. package/native/cmd/ttsc-typia/transform.go +2 -0
  42. package/native/cmd/ttsc-typia/transform_helpers_test.go +17 -3
  43. package/native/core/context/ITypiaContext.go +4 -0
  44. package/native/core/factories/IdentifierFactory.go +10 -0
  45. package/native/core/factories/LiteralFactory.go +16 -0
  46. package/native/core/factories/MetadataCommentTagFactory.go +12 -10
  47. package/native/core/factories/MetadataTypeTagFactory.go +97 -24
  48. package/native/core/factories/MetadataTypeTagSchemaFactory.go +5 -0
  49. package/native/core/factories/internal/metadata/IMetadataIteratorProps.go +6 -2
  50. package/native/core/factories/internal/metadata/emplace_metadata_object.go +49 -15
  51. package/native/core/factories/literal_factory_null_survives_objects_test.go +43 -0
  52. package/native/core/factories/metadata_comment_tag_factory_string_length_helpers_test.go +55 -0
  53. package/native/core/factories/metadata_type_tag_factory_reports_all_invalid_candidates_test.go +73 -0
  54. package/native/core/programmers/llm/LlmApplicationProgrammer.go +1 -0
  55. package/native/core/programmers/llm/LlmEvaluationProgrammer.go +555 -0
  56. package/native/core/programmers/llm/LlmParametersProgrammer.go +6 -5
  57. package/native/core/programmers/llm/LlmReferenceDescription.go +113 -0
  58. package/native/core/programmers/llm/llm_reference_description_test.go +134 -0
  59. package/native/core/schemas/metadata/MetadataDependency.go +9 -0
  60. package/native/transform/CallExpressionTransformer.go +1 -0
  61. package/native/transform/FileTransformer.go +5 -0
  62. package/native/transform/features/llm/LlmEvaluationProbabilityDeclarations.go +94 -0
  63. package/native/transform/features/llm/LlmEvaluationProbabilityDeclarations_test.go +50 -0
  64. package/native/transform/features/llm/LlmEvaluationTransformer.go +59 -0
  65. package/native/transform/features/llm/LlmSchemaTransformer.go +14 -11
  66. package/native/transform/features/llm/llm_transformer_entry_coverage_test.go +29 -27
  67. package/package.json +3 -3
  68. package/src/internal/_createLlmEvaluation.ts +678 -0
  69. package/src/internal/_stringLengthGte.ts +6 -0
  70. package/src/internal/_stringLengthLte.ts +6 -0
  71. package/src/llm.ts +64 -0
  72. package/src/re-exports.ts +1 -0
@@ -0,0 +1,199 @@
1
+ package main
2
+
3
+ import (
4
+ "strings"
5
+ "testing"
6
+ )
7
+
8
+ // TestLlmEvaluationRejectsInvalidProbability verifies typia.llm.evaluation
9
+ // rejects every malformed or misplaced probability requirement.
10
+ //
11
+ // A requirement is a probability, so anything outside `[0, 1]` or not a
12
+ // number is a compile error; two requirements on one target are ambiguous
13
+ // rather than silently resolved; and `@probability` on an object has no
14
+ // decision to govern. Once one choice, score, or set member declares a
15
+ // requirement, every member must be covered directly or by a property default.
16
+ // Each case pins the accessor and, for members, the member literal, so a
17
+ // rejection lands on the declaration the author must fix. A valid requirement
18
+ // of every spelling, in a separate project, is the positive twin.
19
+ //
20
+ // 1. Build one project with one call per invalid requirement, and a separate
21
+ // project with valid requirements.
22
+ // 2. Require the first build to fail through the transform-diagnostic path.
23
+ // 3. Require each rejected accessor with its message, and the valid project
24
+ // to compile.
25
+ func TestLlmEvaluationRejectsInvalidProbability(t *testing.T) {
26
+ errText := llmEvaluationDiagnosticsBuild(t, "probability", llmEvaluationProbabilitySource)
27
+ for _, expected := range []string{
28
+ "- $input.tagOver\n - LLM evaluation tags.Probability must be in [0, 1], but got 1.5.",
29
+ "- $input.tagUnder\n - LLM evaluation tags.Probability must be in [0, 1], but got -0.1.",
30
+ "- $input.commentText\n - LLM evaluation @probability must be a number in [0, 1], but got \"high\".",
31
+ "- $input.commentEmpty\n - LLM evaluation @probability must be a number in [0, 1], but got \"\".",
32
+ "- $input.commentOver\n - LLM evaluation @probability must be in [0, 1], but got 2.",
33
+ "- $input.commentTwice\n - LLM evaluation @probability is declared more than once; keep only one.",
34
+ "- $input.both\n - LLM evaluation boolean has both tags.Probability and @probability; keep only one.",
35
+ "- $input.memberTag\n - LLM evaluation tags.Probability must be in [0, 1], but got 1.2. (member \"risky\")",
36
+ "- $input.memberComment\n - LLM evaluation @probability must be in [0, 1], but got 3. (member \"bad\")",
37
+ "- $input.scoreComment\n - LLM evaluation @probability must be a number in [0, 1], but got \"x\". (member 1)",
38
+ "- $input.setMember\n - LLM evaluation tags.Probability must be in [0, 1], but got 7. (member \"card\")",
39
+ "- $input.partialTag\n - LLM evaluation probability requirements must cover every member once one member declares one; add tags.Probability or @probability to member \"plain\", or add a property @probability default.",
40
+ "- $input.partialComment\n - LLM evaluation probability requirements must cover every member once one member declares one; add tags.Probability or @probability to member \"plain\", or add a property @probability default.",
41
+ "- $input.nested\n - LLM evaluation @probability must be on a boolean, choice, score, or set property, not on an object.",
42
+ // two tags on one member fail typia's generic exclusive-tag check first
43
+ "- __type.tagTwice: string & Probability0.5 & Probability0.6\n - the property [\"typia.tag\"] kind 'probability' can't be duplicated.",
44
+ // a non-literal value fails typia's generic tag check (samchon/typia#2400)
45
+ "- __type.tagWide: boolean & Probabilitynumber\n - the property [\"typia.tag.value\"] must be a literal, literal tuple, object, or undefined type.",
46
+ } {
47
+ if !strings.Contains(errText, expected) {
48
+ t.Fatalf("llm.evaluation probability diagnostic missing %q:\n%s", expected, errText)
49
+ }
50
+ }
51
+ llmEvaluationAccepts(t, "probability-valid", llmEvaluationProbabilityValidSource)
52
+ }
53
+
54
+ const llmEvaluationProbabilityValidSource = `import typia, { tags } from "typia";
55
+
56
+ enum Team {
57
+ /**
58
+ * Payments
59
+ *
60
+ * @probability 0.5
61
+ */
62
+ billing = "billing",
63
+ /**
64
+ * Outages
65
+ *
66
+ * @probability 0.75
67
+ */
68
+ technical = "technical",
69
+ }
70
+
71
+ typia.llm.evaluation<IValid>();
72
+
73
+ interface IValid {
74
+ /** Tagged? */
75
+ tagged: boolean & tags.Probability<0>;
76
+ /** Tagged at one? */
77
+ taggedOne: boolean & tags.Probability<1>;
78
+ /**
79
+ * Commented?
80
+ *
81
+ * @probability 1
82
+ */
83
+ commented: boolean;
84
+ /** Team? */
85
+ team: Team;
86
+ /** Action? */
87
+ action:
88
+ | ("risky" & tags.Probability<0.9>)
89
+ | ("safe" & tags.Probability<0>);
90
+ /**
91
+ * Products?
92
+ *
93
+ * @probability 0.7
94
+ */
95
+ products: Array<("card" & tags.Probability<0.9>) | "loan">;
96
+ }
97
+ `
98
+
99
+ const llmEvaluationProbabilitySource = `import typia, { tags } from "typia";
100
+
101
+ enum Bad {
102
+ /** Fine */
103
+ fine = "fine",
104
+ /**
105
+ * Broken
106
+ *
107
+ * @probability 3
108
+ */
109
+ bad = "bad",
110
+ }
111
+
112
+ enum Level {
113
+ /**
114
+ * Low
115
+ *
116
+ * @probability x
117
+ */
118
+ low = 1,
119
+ /** High */
120
+ high = 2,
121
+ }
122
+
123
+ enum Partial {
124
+ /**
125
+ * Gated
126
+ *
127
+ * @probability 0.8
128
+ */
129
+ gated = "gated",
130
+ /** Plain */
131
+ plain = "plain",
132
+ }
133
+
134
+ typia.llm.evaluation<{
135
+ /** Wide? */ tagWide: boolean & tags.Probability<number>;
136
+ }>();
137
+
138
+ typia.llm.evaluation<{
139
+ /** Over? */ tagOver: boolean & tags.Probability<1.5>;
140
+ /** Under? */ tagUnder: boolean & tags.Probability<-0.1>;
141
+ /**
142
+ * Text?
143
+ *
144
+ * @probability high
145
+ */
146
+ commentText: boolean;
147
+ /**
148
+ * Empty?
149
+ *
150
+ * @probability
151
+ */
152
+ commentEmpty: boolean;
153
+ /**
154
+ * Over?
155
+ *
156
+ * @probability 2
157
+ */
158
+ commentOver: boolean;
159
+ /**
160
+ * Twice?
161
+ *
162
+ * @probability 0.5
163
+ * @probability 0.6
164
+ */
165
+ commentTwice: boolean;
166
+ /**
167
+ * Both?
168
+ *
169
+ * @probability 0.7
170
+ */
171
+ both: boolean & tags.Probability<0.8>;
172
+ /** Member tag? */
173
+ memberTag: ("risky" & tags.Probability<1.2>) | "safe";
174
+ /** Member comment? */
175
+ memberComment: Bad;
176
+ /** Score comment? */
177
+ scoreComment: Level;
178
+ /** Set member? */
179
+ setMember: Array<("card" & tags.Probability<7>) | "loan">;
180
+ /** Partial tag? */
181
+ partialTag: ("gated" & tags.Probability<0.8>) | "plain";
182
+ /** Partial comment? */
183
+ partialComment: Partial;
184
+ /**
185
+ * Nested?
186
+ *
187
+ * @probability 0.5
188
+ */
189
+ nested: {
190
+ /** Inner? */
191
+ inner: boolean;
192
+ };
193
+ }>();
194
+
195
+ typia.llm.evaluation<{
196
+ /** Twice? */
197
+ tagTwice: ("a" & tags.Probability<0.5> & tags.Probability<0.6>) | "b";
198
+ }>();
199
+ `
@@ -0,0 +1,94 @@
1
+ package main
2
+
3
+ import (
4
+ "strings"
5
+ "testing"
6
+ )
7
+
8
+ // TestLlmEvaluationRejectsUnsupportedRefinements verifies every validation
9
+ // tag outside the decoder's contract is a compile error.
10
+ //
11
+ // A decoded decision must satisfy every constraint in T. The evaluator cannot
12
+ // turn a selected literal into one satisfying an unrelated refinement.
13
+ //
14
+ // 1. Declare invalid string, number, boolean, and object refinements.
15
+ // 2. Require a transform diagnostic at each decision path.
16
+ // 3. Accept neighboring annotation-only tags.
17
+ func TestLlmEvaluationRejectsUnsupportedRefinements(t *testing.T) {
18
+ errText := llmEvaluationDiagnosticsBuild(t, "refinements", `import typia, { tags } from "typia";
19
+
20
+ typia.llm.evaluation<{
21
+ /** Which team? */
22
+ team: ("a" & tags.MinLength<2>) | "b";
23
+ /** Which level? */
24
+ level: (0 & tags.Minimum<1>) | 2;
25
+ /** Which product? */
26
+ products: Array<( "a" & tags.MinLength<2>) | "b">;
27
+ nested: {
28
+ /** Which status? */
29
+ status: ("old" & tags.Pattern<"^new$">) | "new";
30
+ };
31
+ }>();
32
+
33
+ type TrueOnly = tags.TagBase<{
34
+ target: "boolean";
35
+ kind: "trueOnly";
36
+ value: undefined;
37
+ validate: "$input === true";
38
+ }>;
39
+ typia.llm.evaluation<{
40
+ /** Is it permitted? */
41
+ permitted: boolean & TrueOnly;
42
+ }>();
43
+
44
+ type GuardedProbability = tags.TagBase<{
45
+ target: "boolean";
46
+ kind: "probability";
47
+ value: 0.8;
48
+ validate: "$input === true";
49
+ }>;
50
+ typia.llm.evaluation<{
51
+ /** Is it allowed? */
52
+ allowed: boolean & GuardedProbability;
53
+ }>();
54
+
55
+ type ObjectOnly = tags.TagBase<{
56
+ target: "object";
57
+ kind: "objectOnly";
58
+ value: undefined;
59
+ validate: "true";
60
+ }>;
61
+ typia.llm.evaluation<{
62
+ child: ({ /** Is it active? */ active: boolean } & ObjectOnly);
63
+ }>();
64
+
65
+ interface IRoot {
66
+ /** Is it active? */
67
+ active: boolean;
68
+ }
69
+ typia.llm.evaluation<IRoot & ObjectOnly>();
70
+ `)
71
+ for _, expected := range []string{
72
+ "- $input.team\n - LLM evaluation does not support",
73
+ "- $input.level\n - LLM evaluation does not support",
74
+ "- $input.products\n - LLM evaluation does not support",
75
+ "- $input.nested.status\n - LLM evaluation does not support",
76
+ "- $input.permitted\n - LLM evaluation does not support",
77
+ "- $input.allowed\n - LLM evaluation does not support",
78
+ "- $input.child\n - LLM evaluation does not support",
79
+ "- $input\n - LLM evaluation does not support",
80
+ } {
81
+ if !strings.Contains(errText, expected) {
82
+ t.Fatalf("missing unsupported refinement diagnostic %q:\n%s", expected, errText)
83
+ }
84
+ }
85
+ llmEvaluationAccepts(t, "annotation-refinements", `import typia, { tags } from "typia";
86
+
87
+ typia.llm.evaluation<{
88
+ /** Is it urgent? */
89
+ urgent: boolean & tags.Default<true>;
90
+ /** Which level? */
91
+ level: (0 & tags.Example<0>) | 1;
92
+ }>();
93
+ `)
94
+ }
@@ -0,0 +1,232 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ // TestLlmEvaluationRejectsUndecidableTypes verifies typia.llm.evaluation
11
+ // rejects every position an evaluation model cannot answer.
12
+ //
13
+ // An evaluation model answers only closed sets: booleans, one option of a
14
+ // literal union, a level of a numeric union, and membership in a literal set.
15
+ // Every other shape, a leaf without the JSDoc text that becomes its question,
16
+ // and a decision type that asks nothing must fail at compile time on the exact
17
+ // accessor, instead of emitting a question the model cannot receive or a value
18
+ // decode() can never produce. A valid neighbor type in the same project is
19
+ // the positive twin that must keep compiling.
20
+ //
21
+ // 1. Build one project with one call per rejected shape, and a separate
22
+ // project with a valid decision type.
23
+ // 2. Require the first build to fail through the transform-diagnostic path.
24
+ // 3. Require each rejected accessor with its message, and the valid project
25
+ // to compile.
26
+ func TestLlmEvaluationRejectsUndecidableTypes(t *testing.T) {
27
+ errText := llmEvaluationDiagnosticsBuild(t, "undecidable", llmEvaluationUndecidableSource)
28
+ for _, expected := range []string{
29
+ "- $input\n - LLM evaluation type must be an object type.",
30
+ "- $input\n - LLM evaluation type must be a single object type.",
31
+ "- $input\n - LLM evaluation type must have at least one decision property.",
32
+ "- $input\n - LLM evaluation does not support dynamic keys.",
33
+ "- $input.text\n - LLM evaluation supports only boolean",
34
+ "- $input.count\n - LLM evaluation supports only boolean",
35
+ "- $input.big\n - LLM evaluation supports only boolean",
36
+ "- $input.template\n - LLM evaluation supports only boolean",
37
+ "- $input.date\n - LLM evaluation supports only boolean",
38
+ "- $input.tuple\n - LLM evaluation supports only boolean",
39
+ "- $input.map\n - LLM evaluation supports only boolean",
40
+ "- $input.sole\n - LLM evaluation does not support a single literal value, because it decides nothing.",
41
+ "- $input.yes\n - LLM evaluation does not support a single literal value, because it decides nothing.",
42
+ "- $input.mixed\n - LLM evaluation does not support union types mixing different question kinds.",
43
+ "- $input.flagOrLabel\n - LLM evaluation does not support union types mixing different question kinds.",
44
+ "- $input.bigints\n - LLM evaluation does not support bigint literal types.",
45
+ "- $input.nullable\n - LLM evaluation does not support nullable properties.",
46
+ "- $input.optional\n - LLM evaluation does not support optional, undefined, void, or never properties, because every property of the result needs an answer.",
47
+ "- $input.undefinable\n - LLM evaluation does not support optional, undefined, void, or never properties, because every property of the result needs an answer.",
48
+ "- $input.voided\n - LLM evaluation does not support optional, undefined, void, or never properties, because every property of the result needs an answer.",
49
+ "- $input.impossible\n - LLM evaluation does not support optional, undefined, void, or never properties, because every property of the result needs an answer.",
50
+ "- $input.objects\n - LLM evaluation does not support union types.",
51
+ "- $input.rows\n - LLM evaluation supports only arrays of a string literal union or string enum.",
52
+ "- $input.levels\n - LLM evaluation supports only arrays of a string literal union or string enum.",
53
+ "- $input.tagged\n - LLM evaluation does not support array type tags, because the answered members decide the array.",
54
+ "- $input.record\n - LLM evaluation does not support dynamic keys.",
55
+ "- $input.empty\n - LLM evaluation object must have at least one decision property.",
56
+ "- $input.node.child\n - LLM evaluation does not support recursive types.",
57
+ "- $input.undocumented\n - LLM evaluation property must have a JSDoc description, because it is the question text.",
58
+ "- $input.blank\n - LLM evaluation property must have a JSDoc description, because it is the question text.",
59
+ "- $input.nbsp\n - LLM evaluation property must have a JSDoc description, because it is the question text.",
60
+ "- $input.tagOnly\n - LLM evaluation property must have a JSDoc description, because it is the question text.",
61
+ "- $input.secret\n - LLM evaluation does not support hidden properties, because every property of the result needs an answer.",
62
+ "- $input.method\n - LLM evaluation does not support function properties.",
63
+ "- $input.anything\n - LLM evaluation does not support any or unknown types, because an evaluation model answers only closed sets.",
64
+ "- $input.opaque\n - LLM evaluation does not support any or unknown types, because an evaluation model answers only closed sets.",
65
+ } {
66
+ if !strings.Contains(errText, expected) {
67
+ t.Fatalf("llm.evaluation diagnostic missing %q:\n%s", expected, errText)
68
+ }
69
+ }
70
+ llmEvaluationAccepts(t, "undecidable-valid", llmEvaluationUndecidableValidSource)
71
+ }
72
+
73
+ // llmEvaluationDiagnosticsBuild builds a fixture project that must fail through
74
+ // the transform-diagnostic path and returns its normalized stderr.
75
+ func llmEvaluationDiagnosticsBuild(t *testing.T, name string, source string) string {
76
+ t.Helper()
77
+ dir := llmEvaluationProject(t, name, source)
78
+ _, errText, code := ttscTypiaTestCapture(func() int {
79
+ return runBuild([]string{
80
+ "--cwd", dir,
81
+ "--tsconfig", "tsconfig.json",
82
+ "--emit",
83
+ "--outDir", filepath.Join(dir, "dist"),
84
+ })
85
+ })
86
+ if code != 3 {
87
+ t.Fatalf("%s llm.evaluation build should fail with code 3, got %d\nstderr=%s", name, code, errText)
88
+ }
89
+ normalized := strings.ReplaceAll(filepath.ToSlash(errText), "\r\n", "\n")
90
+ if !strings.Contains(normalized, "error TS(typia.llm.evaluation):") {
91
+ t.Fatalf("%s llm.evaluation diagnostic code missing:\n%s", name, errText)
92
+ }
93
+ return normalized
94
+ }
95
+
96
+ // llmEvaluationAccepts builds a fixture project that must compile without any
97
+ // diagnostic. A positive twin lives in its own project because diagnostic
98
+ // line numbers are not a reliable way to prove a call stayed quiet.
99
+ func llmEvaluationAccepts(t *testing.T, name string, source string) {
100
+ t.Helper()
101
+ dir := llmEvaluationProject(t, name, source)
102
+ _, errText, code := ttscTypiaTestCapture(func() int {
103
+ return runBuild([]string{
104
+ "--cwd", dir,
105
+ "--tsconfig", "tsconfig.json",
106
+ "--emit",
107
+ "--outDir", filepath.Join(dir, "dist"),
108
+ })
109
+ })
110
+ if code != 0 {
111
+ t.Fatalf("%s positive twin must compile, got code %d\nstderr=%s", name, code, errText)
112
+ }
113
+ }
114
+
115
+ // llmEvaluationProject writes a single-file fixture project and returns its
116
+ // directory, removed when the test ends.
117
+ func llmEvaluationProject(t *testing.T, name string, source string) string {
118
+ t.Helper()
119
+ root := ttscTypiaTestRepoRoot(t)
120
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
121
+ if err := os.MkdirAll(base, 0o755); err != nil {
122
+ t.Fatalf("mkdir temp base: %v", err)
123
+ }
124
+ dir, err := os.MkdirTemp(base, "llm-evaluation-"+name+"-")
125
+ if err != nil {
126
+ t.Fatalf("create temp fixture: %v", err)
127
+ }
128
+ t.Cleanup(func() {
129
+ _ = os.RemoveAll(dir)
130
+ })
131
+ src := filepath.Join(dir, "src")
132
+ if err := os.MkdirAll(src, 0o755); err != nil {
133
+ t.Fatalf("mkdir fixture src: %v", err)
134
+ }
135
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(llmEvaluationDiagnosticsTSConfig), 0o644); err != nil {
136
+ t.Fatalf("write tsconfig: %v", err)
137
+ }
138
+ if err := os.WriteFile(filepath.Join(src, "main.ts"), []byte(source), 0o644); err != nil {
139
+ t.Fatalf("write source: %v", err)
140
+ }
141
+ return dir
142
+ }
143
+
144
+ const llmEvaluationDiagnosticsTSConfig = `{
145
+ "compilerOptions": {
146
+ "target": "ES2022",
147
+ "module": "commonjs",
148
+ "moduleResolution": "bundler",
149
+ "ignoreDeprecations": "6.0",
150
+ "types": ["*"],
151
+ "esModuleInterop": true,
152
+ "strict": true,
153
+ "skipLibCheck": true,
154
+ "rootDir": "src",
155
+ "outDir": "dist"
156
+ },
157
+ "include": ["src"]
158
+ }
159
+ `
160
+
161
+ const llmEvaluationUndecidableValidSource = `import typia from "typia";
162
+
163
+ interface IValid {
164
+ /** Is it urgent? */
165
+ urgent: boolean;
166
+ /** Which team? */
167
+ team: "billing" | "technical";
168
+ }
169
+ typia.llm.evaluation<IValid>();
170
+ `
171
+
172
+ const llmEvaluationUndecidableSource = `import typia, { tags } from "typia";
173
+
174
+ interface IFlag {
175
+ /** Is it urgent? */
176
+ urgent: boolean;
177
+ }
178
+ interface IOther {
179
+ /** Is it late? */
180
+ late: boolean;
181
+ }
182
+ typia.llm.evaluation<any>();
183
+ typia.llm.evaluation<IFlag | IOther>();
184
+ typia.llm.evaluation<{}>();
185
+ typia.llm.evaluation<Record<string, boolean>>();
186
+
187
+ interface INode {
188
+ /** Is it a leaf? */
189
+ leaf: boolean;
190
+ child: INode;
191
+ }
192
+
193
+ typia.llm.evaluation<{
194
+ /** Text? */ text: string;
195
+ /** Count? */ count: number;
196
+ /** Big? */ big: bigint;
197
+ /** Template? */ template: ` + "`id-${string}`" + `;
198
+ /** Date? */ date: Date;
199
+ /** Tuple? */ tuple: [boolean];
200
+ /** Map? */ map: Map<string, boolean>;
201
+ /** Sole? */ sole: "only";
202
+ /** Yes? */ yes: true;
203
+ /** Mixed? */ mixed: "one" | 2;
204
+ /** Flag or label? */ flagOrLabel: boolean | "label";
205
+ /** Bigints? */ bigints: 1n | 2n;
206
+ /** Nullable? */ nullable: boolean | null;
207
+ /** Optional? */ optional?: boolean;
208
+ /** Undefinable? */ undefinable: boolean | undefined;
209
+ /** Voided? */ voided: void;
210
+ /** Impossible? */ impossible: never;
211
+ objects: { /** A? */ a: boolean } | { /** B? */ b: boolean };
212
+ /** Rows? */ rows: Array<{ /** Row? */ row: boolean }>;
213
+ /** Levels? */ levels: Array<1 | 2>;
214
+ /** Tagged? */ tagged: Array<"a" | "b"> & tags.MinItems<1>;
215
+ record: Record<string, boolean>;
216
+ empty: {};
217
+ node: INode;
218
+ undocumented: boolean;
219
+ /** */ blank: boolean;
220
+ /** @probability 0.5 */ tagOnly: boolean;
221
+ /**   */ nbsp: boolean;
222
+ /**
223
+ * Secret?
224
+ *
225
+ * @hidden
226
+ */
227
+ secret: boolean;
228
+ /** Method? */ method: () => boolean;
229
+ /** Anything? */ anything: any;
230
+ /** Opaque? */ opaque: unknown;
231
+ }>();
232
+ `
@@ -0,0 +1,94 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ // TestMetadataTypeTagSoleMalformedDiagnostic verifies a malformed type tag is
11
+ // reported even when no valid tag accompanies it.
12
+ //
13
+ // MetadataTypeTagFactory.Analyze collects a message for every malformed tag
14
+ // object, but returned early when no tag object survived, before those messages
15
+ // reached the error list. A sole malformed tag therefore vanished: the
16
+ // constraint its author wrote was enforced nowhere, while the same tag beside a
17
+ // valid one was reported (samchon/typia#2400). The now-visible message must
18
+ // also name the malformed `validate` property instead of blaming `target`.
19
+ // The accompanied case keeps its message, and a valid sole tag is the negative
20
+ // twin that must keep compiling.
21
+ //
22
+ // 1. Build one project whose calls carry a sole non-literal tag value, the
23
+ // same value beside a valid tag, and the same value through
24
+ // `typia.llm.evaluation`, and a separate project with a valid sole tag.
25
+ // 2. Require the first build to fail through the transform-diagnostic path.
26
+ // 3. Require every malformed call's messages, and the valid project to
27
+ // compile.
28
+ func TestMetadataTypeTagSoleMalformedDiagnostic(t *testing.T) {
29
+ root := ttscTypiaTestRepoRoot(t)
30
+ base := filepath.Join(root, "packages", "typia", "native", ".tmp-ttsc-typia-tests")
31
+ if err := os.MkdirAll(base, 0o755); err != nil {
32
+ t.Fatalf("mkdir temp base: %v", err)
33
+ }
34
+ dir, err := os.MkdirTemp(base, "sole-malformed-tag-")
35
+ if err != nil {
36
+ t.Fatalf("create temp fixture: %v", err)
37
+ }
38
+ t.Cleanup(func() {
39
+ _ = os.RemoveAll(dir)
40
+ })
41
+ if err := os.MkdirAll(filepath.Join(dir, "src"), 0o755); err != nil {
42
+ t.Fatalf("mkdir fixture src: %v", err)
43
+ }
44
+ if err := os.WriteFile(filepath.Join(dir, "tsconfig.json"), []byte(llmEvaluationDiagnosticsTSConfig), 0o644); err != nil {
45
+ t.Fatalf("write tsconfig: %v", err)
46
+ }
47
+ if err := os.WriteFile(filepath.Join(dir, "src", "main.ts"), []byte(metadataTypeTagSoleMalformedSource), 0o644); err != nil {
48
+ t.Fatalf("write source: %v", err)
49
+ }
50
+ _, errText, code := ttscTypiaTestCapture(func() int {
51
+ return runBuild([]string{
52
+ "--cwd", dir,
53
+ "--tsconfig", "tsconfig.json",
54
+ "--emit",
55
+ "--outDir", filepath.Join(dir, "dist"),
56
+ })
57
+ })
58
+ if code != 3 {
59
+ t.Fatalf("sole malformed tag build should fail with code 3, got %d\nstderr=%s", code, errText)
60
+ }
61
+ normalized := strings.ReplaceAll(filepath.ToSlash(errText), "\r\n", "\n")
62
+ const value = " - the property [\"typia.tag.value\"] must be a literal, literal tuple, object, or undefined type."
63
+ const validate = " - the property [\"typia.tag.validate\"] must be a string literal, or an object whose keys are 'boolean', 'bigint', 'number', 'string', 'array', or 'object'."
64
+ for _, expected := range []string{
65
+ "- number & Minimum<number>\n" + value + "\n" + validate,
66
+ "- number & Minimum<number> & Maximum<10>\n" + value + "\n" + validate,
67
+ "- Probe.flag: boolean & Probabilitynumber\n" + value,
68
+ } {
69
+ if !strings.Contains(normalized, expected) {
70
+ t.Fatalf("sole malformed tag diagnostic missing %q:\n%s", expected, errText)
71
+ }
72
+ }
73
+ if strings.Contains(normalized, "typia.tag.target") {
74
+ t.Fatalf("a malformed validate must not be reported as target:\n%s", errText)
75
+ }
76
+ llmEvaluationAccepts(t, "sole-valid-tag", metadataTypeTagSoleValidSource)
77
+ }
78
+
79
+ const metadataTypeTagSoleValidSource = `import typia, { tags } from "typia";
80
+
81
+ typia.is<number & tags.Minimum<0>>(1);
82
+ `
83
+
84
+ const metadataTypeTagSoleMalformedSource = `import typia, { tags } from "typia";
85
+
86
+ type Probe = {
87
+ /** Is it flagged? */
88
+ flag: boolean & tags.Probability<number>;
89
+ };
90
+
91
+ typia.is<number & tags.Minimum<number>>(1);
92
+ typia.is<number & tags.Minimum<number> & tags.Maximum<10>>(1);
93
+ typia.llm.evaluation<Probe>();
94
+ `
@@ -34,8 +34,9 @@ func TestTemplateLiteralTypeTagsTransform(t *testing.T) {
34
34
  if code != 0 {
35
35
  t.Fatalf("template literal tags transform failed: code=%d stderr=\n%s", code, errText)
36
36
  }
37
- if !strings.Contains(out, `require("typia/lib/internal/_stringLength")`) ||
38
- !strings.Contains(out, "._stringLength(input) <= 30") ||
37
+ if !strings.Contains(out, `require("typia/lib/internal/_stringLengthLte")`) ||
38
+ !strings.Contains(out, "._stringLengthLte(input, 30)") ||
39
+ !strings.Contains(out, `require("typia/lib/internal/_stringLengthGte")`) ||
39
40
  !strings.Contains(out, "RegExp(/^prefix(.*)postfix$/)") {
40
41
  t.Fatalf("emitted checker should combine the template pattern with its tags:\n%s", out)
41
42
  }
@@ -9,6 +9,7 @@ import (
9
9
  "path/filepath"
10
10
  "sort"
11
11
  "strings"
12
+ "sync"
12
13
 
13
14
  shimast "github.com/microsoft/typescript-go/shim/ast"
14
15
  shimcompiler "github.com/microsoft/typescript-go/shim/compiler"
@@ -86,6 +87,7 @@ func runTransform(args []string) int {
86
87
  transformDiags := []typiaTransformDiagnostic{}
87
88
  transformOptions := pluginOptions.TransformOptions()
88
89
  extras := nativecontext.ITypiaContext_Extras{
90
+ Shared: &sync.Map{},
89
91
  AddDiagnostic: func(diag *nativecontext.ITypiaDiagnostic) int {
90
92
  transformDiags = append(transformDiags, typiaTransformDiagnosticFrom(diag))
91
93
  return len(transformDiags)