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.
- package/README.md +3 -0
- package/lib/internal/_createLlmEvaluation.d.ts +1 -0
- package/lib/internal/_createLlmEvaluation.js +486 -0
- package/lib/internal/_createLlmEvaluation.js.map +1 -0
- package/lib/internal/_createLlmEvaluation.mjs +382 -0
- package/lib/internal/_createLlmEvaluation.mjs.map +1 -0
- package/lib/internal/_stringLengthGte.d.ts +1 -0
- package/lib/internal/_stringLengthGte.js +14 -0
- package/lib/internal/_stringLengthGte.js.map +1 -0
- package/lib/internal/_stringLengthGte.mjs +11 -0
- package/lib/internal/_stringLengthGte.mjs.map +1 -0
- package/lib/internal/_stringLengthLte.d.ts +1 -0
- package/lib/internal/_stringLengthLte.js +14 -0
- package/lib/internal/_stringLengthLte.js.map +1 -0
- package/lib/internal/_stringLengthLte.mjs +11 -0
- package/lib/internal/_stringLengthLte.mjs.map +1 -0
- package/lib/llm.d.ts +54 -1
- package/lib/llm.js +5 -0
- package/lib/llm.js.map +1 -1
- package/lib/llm.mjs +6 -1
- package/lib/llm.mjs.map +1 -1
- package/lib/re-exports.d.ts +1 -1
- package/lib/re-exports.js.map +1 -1
- package/native/cmd/ttsc-typia/build.go +2 -0
- package/native/cmd/ttsc-typia/dynamic_key_tags_transform_test.go +1 -1
- package/native/cmd/ttsc-typia/llm_evaluation_accepts_property_probability_test.go +27 -0
- package/native/cmd/ttsc-typia/llm_evaluation_accepts_unselected_source_probability_test.go +26 -0
- package/native/cmd/ttsc-typia/llm_evaluation_complete_object_shape_test.go +96 -0
- package/native/cmd/ttsc-typia/llm_evaluation_ignores_declaration_probability_without_call_test.go +18 -0
- package/native/cmd/ttsc-typia/llm_evaluation_plan_emission_transform_test.go +90 -0
- package/native/cmd/ttsc-typia/llm_evaluation_program_scan_dependencies_test.go +69 -0
- package/native/cmd/ttsc-typia/llm_evaluation_rejects_alias_comment_test.go +44 -0
- package/native/cmd/ttsc-typia/llm_evaluation_rejects_alias_refinement_test.go +45 -0
- package/native/cmd/ttsc-typia/llm_evaluation_rejects_computed_method_probability_test.go +30 -0
- package/native/cmd/ttsc-typia/llm_evaluation_rejects_imported_declaration_probability_test.go +48 -0
- package/native/cmd/ttsc-typia/llm_evaluation_rejects_invalid_probability_test.go +199 -0
- package/native/cmd/ttsc-typia/llm_evaluation_rejects_refinements_test.go +94 -0
- package/native/cmd/ttsc-typia/llm_evaluation_rejects_undecidable_types_test.go +232 -0
- package/native/cmd/ttsc-typia/metadata_type_tag_sole_malformed_diagnostic_test.go +94 -0
- package/native/cmd/ttsc-typia/template_literal_type_tags_transform_test.go +3 -2
- package/native/cmd/ttsc-typia/transform.go +2 -0
- package/native/cmd/ttsc-typia/transform_helpers_test.go +17 -3
- package/native/core/context/ITypiaContext.go +4 -0
- package/native/core/factories/IdentifierFactory.go +10 -0
- package/native/core/factories/LiteralFactory.go +16 -0
- package/native/core/factories/MetadataCommentTagFactory.go +12 -10
- package/native/core/factories/MetadataTypeTagFactory.go +97 -24
- package/native/core/factories/MetadataTypeTagSchemaFactory.go +5 -0
- package/native/core/factories/internal/metadata/IMetadataIteratorProps.go +6 -2
- package/native/core/factories/internal/metadata/emplace_metadata_object.go +49 -15
- package/native/core/factories/literal_factory_null_survives_objects_test.go +43 -0
- package/native/core/factories/metadata_comment_tag_factory_string_length_helpers_test.go +55 -0
- package/native/core/factories/metadata_type_tag_factory_reports_all_invalid_candidates_test.go +73 -0
- package/native/core/programmers/llm/LlmApplicationProgrammer.go +1 -0
- package/native/core/programmers/llm/LlmEvaluationProgrammer.go +555 -0
- package/native/core/programmers/llm/LlmParametersProgrammer.go +6 -5
- package/native/core/programmers/llm/LlmReferenceDescription.go +113 -0
- package/native/core/programmers/llm/llm_reference_description_test.go +134 -0
- package/native/core/schemas/metadata/MetadataDependency.go +9 -0
- package/native/transform/CallExpressionTransformer.go +1 -0
- package/native/transform/FileTransformer.go +5 -0
- package/native/transform/features/llm/LlmEvaluationProbabilityDeclarations.go +94 -0
- package/native/transform/features/llm/LlmEvaluationProbabilityDeclarations_test.go +50 -0
- package/native/transform/features/llm/LlmEvaluationTransformer.go +59 -0
- package/native/transform/features/llm/LlmSchemaTransformer.go +14 -11
- package/native/transform/features/llm/llm_transformer_entry_coverage_test.go +29 -27
- package/package.json +3 -3
- package/src/internal/_createLlmEvaluation.ts +678 -0
- package/src/internal/_stringLengthGte.ts +6 -0
- package/src/internal/_stringLengthLte.ts +6 -0
- package/src/llm.ts +64 -0
- 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/
|
|
38
|
-
!strings.Contains(out, ".
|
|
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)
|