@voxgig/sdkgen 1.3.17 → 1.3.18
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/bin/voxgig-sdkgen +1 -1
- package/dist/cmp/Entity.js +5 -1
- package/dist/cmp/Entity.js.map +1 -1
- package/dist/cmp/Feature.js +2 -1
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/helpers/canonType.js +8 -3
- package/dist/helpers/canonType.js.map +1 -1
- package/dist/helpers/collectDeps.d.ts +1 -1
- package/dist/helpers/collectDeps.js +36 -13
- package/dist/helpers/collectDeps.js.map +1 -1
- package/dist/helpers/naming.d.ts +3 -1
- package/dist/helpers/naming.js +53 -0
- package/dist/helpers/naming.js.map +1 -1
- package/dist/helpers/opShape.d.ts +3 -1
- package/dist/helpers/opShape.js +87 -8
- package/dist/helpers/opShape.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -3
- package/dist/sdkgen.js +6 -2
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +15 -10
- package/project/.sdk/model/target/cpp.aontu +4 -4
- package/project/.sdk/model/target/go-cli.aontu +17 -17
- package/project/.sdk/model/target/go-mcp.aontu +11 -11
- package/project/.sdk/model/target/go.aontu +1 -1
- package/project/.sdk/model/target/java.aontu +4 -4
- package/project/.sdk/model/target/kotlin.aontu +4 -4
- package/project/.sdk/model/target/lean.aontu +51 -0
- package/project/.sdk/model/target/scala.aontu +4 -4
- package/project/.sdk/src/cmp/c/EntityTypes_c.ts +3 -4
- package/project/.sdk/src/cmp/c/Entity_c.ts +3 -3
- package/project/.sdk/src/cmp/c/ReadmeExamplesTest_c.ts +24 -7
- package/project/.sdk/src/cmp/c/TestDirect_c.ts +3 -3
- package/project/.sdk/src/cmp/c/TestEntity_c.ts +1 -1
- package/project/.sdk/src/cmp/clojure/Entity_clojure.ts +1 -1
- package/project/.sdk/src/cmp/clojure/ReadmeExamplesTest_clojure.ts +19 -8
- package/project/.sdk/src/cmp/cpp/EntityTypes_cpp.ts +2 -2
- package/project/.sdk/src/cmp/cpp/Entity_cpp.ts +3 -3
- package/project/.sdk/src/cmp/cpp/MainEntity_cpp.ts +2 -2
- package/project/.sdk/src/cmp/cpp/ReadmeExamplesTest_cpp.ts +136 -0
- package/project/.sdk/src/cmp/cpp/TestDirect_cpp.ts +3 -3
- package/project/.sdk/src/cmp/cpp/Test_cpp.ts +4 -0
- package/project/.sdk/src/cmp/csharp/EntityTypes_csharp.ts +2 -3
- package/project/.sdk/src/cmp/csharp/Entity_csharp.ts +3 -3
- package/project/.sdk/src/cmp/csharp/MainEntity_csharp.ts +2 -2
- package/project/.sdk/src/cmp/csharp/Package_csharp.ts +1 -1
- package/project/.sdk/src/cmp/csharp/ReadmeExamplesTest_csharp.ts +13 -1
- package/project/.sdk/src/cmp/csharp/TestDirect_csharp.ts +3 -3
- package/project/.sdk/src/cmp/dart/EntityTypes_dart.ts +2 -3
- package/project/.sdk/src/cmp/dart/Entity_dart.ts +3 -3
- package/project/.sdk/src/cmp/dart/MainEntity_dart.ts +2 -2
- package/project/.sdk/src/cmp/dart/Main_dart.ts +3 -3
- package/project/.sdk/src/cmp/dart/Package_dart.ts +1 -1
- package/project/.sdk/src/cmp/dart/TestDirect_dart.ts +4 -4
- package/project/.sdk/src/cmp/elixir/EntityTypes_elixir.ts +2 -3
- package/project/.sdk/src/cmp/elixir/Entity_elixir.ts +1 -1
- package/project/.sdk/src/cmp/elixir/ReadmeExamplesTest_elixir.ts +21 -3
- package/project/.sdk/src/cmp/go/Config_go.ts +4 -1
- package/project/.sdk/src/cmp/go/EntityTypes_go.ts +110 -18
- package/project/.sdk/src/cmp/go/Entity_go.ts +3 -3
- package/project/.sdk/src/cmp/go/Main_go.ts +7 -6
- package/project/.sdk/src/cmp/go/Package_go.ts +1 -1
- package/project/.sdk/src/cmp/go/TestDirect_go.ts +3 -3
- package/project/.sdk/src/cmp/go/TestEntity_go.ts +1 -1
- package/project/.sdk/src/cmp/go/utility_go.ts +25 -0
- package/project/.sdk/src/cmp/go-cli/Main_go-cli.ts +9 -1
- package/project/.sdk/src/cmp/go-mcp/Main_go-mcp.ts +9 -1
- package/project/.sdk/src/cmp/haskell/ReadmeExamplesTest_haskell.ts +28 -18
- package/project/.sdk/src/cmp/java/EntityTypes_java.ts +2 -3
- package/project/.sdk/src/cmp/java/Entity_java.ts +3 -3
- package/project/.sdk/src/cmp/java/MainEntity_java.ts +2 -2
- package/project/.sdk/src/cmp/java/Package_java.ts +1 -1
- package/project/.sdk/src/cmp/java/ReadmeExamplesTest_java.ts +23 -1
- package/project/.sdk/src/cmp/java/TestDirect_java.ts +3 -3
- package/project/.sdk/src/cmp/js/EntityTypes_js.ts +2 -3
- package/project/.sdk/src/cmp/js/Entity_js.ts +3 -3
- package/project/.sdk/src/cmp/js/MainEntity_js.ts +2 -2
- package/project/.sdk/src/cmp/js/Main_js.ts +2 -2
- package/project/.sdk/src/cmp/js/TestDirect_js.ts +4 -4
- package/project/.sdk/src/cmp/kotlin/EntityTypes_kotlin.ts +2 -3
- package/project/.sdk/src/cmp/kotlin/Entity_kotlin.ts +3 -3
- package/project/.sdk/src/cmp/kotlin/MainEntity_kotlin.ts +2 -2
- package/project/.sdk/src/cmp/kotlin/Package_kotlin.ts +1 -1
- package/project/.sdk/src/cmp/kotlin/ReadmeExamplesTest_kotlin.ts +27 -2
- package/project/.sdk/src/cmp/kotlin/TestDirect_kotlin.ts +3 -3
- package/project/.sdk/src/cmp/lean/Config_lean.ts +88 -0
- package/project/.sdk/src/cmp/lean/Entity_lean.ts +9 -0
- package/project/.sdk/src/cmp/lean/Gitignore_lean.ts +18 -0
- package/project/.sdk/src/cmp/lean/Main_lean.ts +120 -0
- package/project/.sdk/src/cmp/lean/Package_lean.ts +68 -0
- package/project/.sdk/src/cmp/lean/ReadmeEntity_lean.ts +29 -0
- package/project/.sdk/src/cmp/lean/ReadmeExplanation_lean.ts +19 -0
- package/project/.sdk/src/cmp/lean/ReadmeHowto_lean.ts +25 -0
- package/project/.sdk/src/cmp/lean/ReadmeInstall_lean.ts +38 -0
- package/project/.sdk/src/cmp/lean/ReadmeIntro_lean.ts +25 -0
- package/project/.sdk/src/cmp/lean/ReadmeModel_lean.ts +24 -0
- package/project/.sdk/src/cmp/lean/ReadmeOptions_lean.ts +22 -0
- package/project/.sdk/src/cmp/lean/ReadmeQuick_lean.ts +50 -0
- package/project/.sdk/src/cmp/lean/ReadmeRef_lean.ts +16 -0
- package/project/.sdk/src/cmp/lean/ReadmeTopHowto_lean.ts +16 -0
- package/project/.sdk/src/cmp/lean/ReadmeTopQuick_lean.ts +23 -0
- package/project/.sdk/src/cmp/lean/ReadmeTopTest_lean.ts +18 -0
- package/project/.sdk/src/cmp/lean/Test_lean.ts +207 -0
- package/project/.sdk/src/cmp/lean/utility_lean.ts +78 -0
- package/project/.sdk/src/cmp/lua/EntityTypes_lua.ts +2 -3
- package/project/.sdk/src/cmp/lua/Entity_lua.ts +3 -3
- package/project/.sdk/src/cmp/lua/Package_lua.ts +1 -1
- package/project/.sdk/src/cmp/lua/TestDirect_lua.ts +3 -3
- package/project/.sdk/src/cmp/lua/TestEntity_lua.ts +1 -1
- package/project/.sdk/src/cmp/ocaml/ReadmeExamplesTest_ocaml.ts +29 -14
- package/project/.sdk/src/cmp/perl/Entity_perl.ts +3 -3
- package/project/.sdk/src/cmp/perl/MainEntity_perl.ts +2 -2
- package/project/.sdk/src/cmp/perl/Package_perl.ts +1 -1
- package/project/.sdk/src/cmp/perl/TestDirect_perl.ts +3 -3
- package/project/.sdk/src/cmp/php/EntityTypes_php.ts +2 -3
- package/project/.sdk/src/cmp/php/Entity_php.ts +3 -3
- package/project/.sdk/src/cmp/php/MainEntity_php.ts +2 -2
- package/project/.sdk/src/cmp/php/Package_php.ts +1 -1
- package/project/.sdk/src/cmp/php/TestDirect_php.ts +3 -3
- package/project/.sdk/src/cmp/php/TestEntity_php.ts +1 -1
- package/project/.sdk/src/cmp/py/EntityTypes_py.ts +2 -3
- package/project/.sdk/src/cmp/py/Entity_py.ts +3 -3
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +2 -2
- package/project/.sdk/src/cmp/py/Main_py.ts +2 -2
- package/project/.sdk/src/cmp/py/Package_py.ts +1 -1
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +3 -3
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +1 -1
- package/project/.sdk/src/cmp/rb/EntityTypes_rb.ts +15 -5
- package/project/.sdk/src/cmp/rb/Entity_rb.ts +3 -3
- package/project/.sdk/src/cmp/rb/MainEntity_rb.ts +2 -2
- package/project/.sdk/src/cmp/rb/Package_rb.ts +2 -2
- package/project/.sdk/src/cmp/rb/TestDirect_rb.ts +3 -3
- package/project/.sdk/src/cmp/rb/TestEntity_rb.ts +1 -1
- package/project/.sdk/src/cmp/rust/EntityBase_rust.ts +2 -2
- package/project/.sdk/src/cmp/rust/EntityTypes_rust.ts +3 -4
- package/project/.sdk/src/cmp/rust/Entity_rust.ts +3 -3
- package/project/.sdk/src/cmp/rust/MainEntity_rust.ts +2 -2
- package/project/.sdk/src/cmp/rust/Package_rust.ts +1 -1
- package/project/.sdk/src/cmp/rust/ReadmeExamplesTest_rust.ts +22 -10
- package/project/.sdk/src/cmp/rust/TestDirect_rust.ts +3 -3
- package/project/.sdk/src/cmp/scala/EntityTypes_scala.ts +2 -3
- package/project/.sdk/src/cmp/scala/Entity_scala.ts +3 -3
- package/project/.sdk/src/cmp/scala/MainEntity_scala.ts +2 -2
- package/project/.sdk/src/cmp/scala/ReadmeExamplesTest_scala.ts +22 -3
- package/project/.sdk/src/cmp/scala/TestDirect_scala.ts +3 -3
- package/project/.sdk/src/cmp/scala/Test_scala.ts +1 -1
- package/project/.sdk/src/cmp/swift/EntityTypes_swift.ts +3 -4
- package/project/.sdk/src/cmp/swift/Entity_swift.ts +3 -3
- package/project/.sdk/src/cmp/swift/MainEntity_swift.ts +2 -2
- package/project/.sdk/src/cmp/swift/Package_swift.ts +1 -1
- package/project/.sdk/src/cmp/swift/ReadmeExamplesTest_swift.ts +18 -1
- package/project/.sdk/src/cmp/swift/TestDirect_swift.ts +1 -1
- package/project/.sdk/src/cmp/ts/EntityTypes_ts.ts +2 -3
- package/project/.sdk/src/cmp/ts/Entity_ts.ts +3 -3
- package/project/.sdk/src/cmp/ts/MainEntity_ts.ts +2 -2
- package/project/.sdk/src/cmp/ts/Main_ts.ts +2 -2
- package/project/.sdk/src/cmp/ts/TestDirect_ts.ts +4 -4
- package/project/.sdk/src/cmp/zig/Entity_zig.ts +3 -3
- package/project/.sdk/src/cmp/zig/MainEntity_zig.ts +2 -2
- package/project/.sdk/src/cmp/zig/Package_zig.ts +10 -2
- package/project/.sdk/tm/cpp/test/primary_utility_test.cpp +8 -16
- package/project/.sdk/tm/csharp/test/PrimaryUtilityTest.cs +10 -28
- package/project/.sdk/tm/dart/test/primary_test.dart +7 -0
- package/project/.sdk/tm/go/feature/test_feature.go +33 -1
- package/project/.sdk/tm/go/test/primary_utility_test.go +41 -42
- package/project/.sdk/tm/go/test/runner_test.go +30 -1
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +7 -24
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +18 -1
- package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +8 -0
- package/project/.sdk/tm/kotlin/test/PrimaryUtilityTest.kt +7 -20
- package/project/.sdk/tm/lean/LICENSE +21 -0
- package/project/.sdk/tm/lean/VERSION +1 -0
- package/project/.sdk/tm/lean/src/SdkFeature.lean +171 -0
- package/project/.sdk/tm/lean/src/SdkFeatures.lean +602 -0
- package/project/.sdk/tm/lean/src/SdkJson.lean +134 -0
- package/project/.sdk/tm/lean/src/SdkRuntime.lean +356 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +617 -0
- package/project/.sdk/tm/lean/src/VoxgigStruct.lean +2880 -0
- package/project/.sdk/tm/lean/src/Vregex.lean +412 -0
- package/project/.sdk/tm/lean/src/feature/README.md +5 -0
- package/project/.sdk/tm/lean/src/feature/audit/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/base/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/cache/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/clienttrack/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/debug/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/idempotency/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/metrics/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/netsim/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/paging/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/proxy/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/ratelimit/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/rbac/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/retry/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/streaming/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/telemetry/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/lean/src/feature/timeout/.gitkeep +0 -0
- package/project/.sdk/tm/lean/test/StructCorpus.lean +697 -0
- package/project/.sdk/tm/lean/test/TFeature.lean +268 -0
- package/project/.sdk/tm/lean/test/TPrimaryUtility.lean +349 -0
- package/project/.sdk/tm/lua/test/primary_utility_test.lua +10 -8
- package/project/.sdk/tm/perl/t/primary_utility.t +8 -17
- package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +9 -7
- package/project/.sdk/tm/py/feature/test_feature.py +28 -1
- package/project/.sdk/tm/py/test/test_primary_utility.py +13 -18
- package/project/.sdk/tm/rb/feature/test_feature.rb +24 -1
- package/project/.sdk/tm/rb/test/primary_utility_test.rb +7 -8
- package/project/.sdk/tm/rust/tests/common/mod.rs +37 -1
- package/project/.sdk/tm/rust/tests/primary_utility_test.rs +12 -26
- package/project/.sdk/tm/swift/Tests/ProjectNameSDKTests/PrimaryUtilityTest.swift +7 -8
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +18 -1
- package/project/.sdk/tm/ts/test/utility/PrimaryUtility.test.ts +65 -21
- package/src/cmp/Entity.ts +5 -1
- package/src/cmp/Feature.ts +2 -1
- package/src/helpers/canonType.ts +8 -3
- package/src/helpers/collectDeps.ts +39 -12
- package/src/helpers/naming.ts +59 -0
- package/src/helpers/opShape.ts +99 -12
- package/src/sdkgen.ts +7 -3
|
@@ -59,7 +59,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
|
|
|
59
59
|
// it only applies to entities that declare a list op. Others (e.g. Batch =
|
|
60
60
|
// create/load) have no list endpoint — make_point errors and the stream
|
|
61
61
|
// yields nothing — so skip the stream test for them.
|
|
62
|
-
const hasList = !!(entity.op && (entity.op as any)
|
|
62
|
+
const hasList = !!(entity.op && (entity.op as any)?.list)
|
|
63
63
|
|
|
64
64
|
const basicflow: ModelEntityFlow | undefined =
|
|
65
65
|
getModelPath(model, `main.${KIT}.flow.Basic${nom(entity, 'Name')}Flow`)
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
canonKey,
|
|
8
8
|
each,
|
|
9
9
|
exampleVarName,
|
|
10
|
+
names,
|
|
10
11
|
} from '@voxgig/sdkgen'
|
|
11
12
|
|
|
12
13
|
import {
|
|
@@ -149,7 +150,31 @@ function clean(o: any) {
|
|
|
149
150
|
}
|
|
150
151
|
|
|
151
152
|
|
|
153
|
+
|
|
154
|
+
// The Go identifier fragment for a feature's generated constructor
|
|
155
|
+
// (New<Fname>Feature / New<Fname>FeatureFunc).
|
|
156
|
+
//
|
|
157
|
+
// SHARED because the identifier is DECLARED in Main_go.ts (registry.go and the
|
|
158
|
+
// root init()) and REFERENCED in Config_go.ts (makeFeature). Two copies of the
|
|
159
|
+
// derivation is a latent undefined-identifier bug in the generated SDK: they
|
|
160
|
+
// were both `name.charAt(0).toUpperCase() + name.slice(1)`, consistently wrong
|
|
161
|
+
// for a name needing real normalisation but at least agreeing, until one side
|
|
162
|
+
// was fixed alone and `rate_limit` became NewRateLimitFeatureFunc in the
|
|
163
|
+
// registry and NewRate_limitFeatureFunc in config.
|
|
164
|
+
//
|
|
165
|
+
// Uses the jostraca-derived PascalCase `Name` (deriving it if absent), which is
|
|
166
|
+
// what Main_ts.ts uses, so a hyphenated or underscored feature name yields a
|
|
167
|
+
// legal Go identifier instead of `NewRate-limitFeatureFunc`.
|
|
168
|
+
function goFeatureName(feat: any): string {
|
|
169
|
+
if (null == feat.Name) {
|
|
170
|
+
names(feat, feat.name)
|
|
171
|
+
}
|
|
172
|
+
return feat.Name
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
|
|
152
176
|
export {
|
|
177
|
+
goFeatureName,
|
|
153
178
|
clean,
|
|
154
179
|
exampleValue,
|
|
155
180
|
formatGoMap,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as Path from 'node:path'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
cmp, each,
|
|
5
|
+
cmp, each, deriveEntityNames,
|
|
6
6
|
File, Content, Fragment, Slot,
|
|
7
7
|
} from '@voxgig/sdkgen'
|
|
8
8
|
|
|
@@ -40,6 +40,14 @@ const Main = cmp(function Main(props: any) {
|
|
|
40
40
|
const cliModule = `github.com/${org}/${model.name}-sdk/go-cli`
|
|
41
41
|
|
|
42
42
|
const entityMap: any = getModelPath(model, `main.${KIT}.entity`)
|
|
43
|
+
// Derive each entity's PascalCase `Name` here rather than relying on another
|
|
44
|
+
// TARGET having run first. This target sets `phase.entity: { active: false }`
|
|
45
|
+
// (see model/target/go-{cli,mcp}.aontu), so create-sdkgen's Root.ts never
|
|
46
|
+
// calls names() for it — the accessor emitted below used to read `Name` off
|
|
47
|
+
// objects the `go` target happened to have named as a side effect, which
|
|
48
|
+
// yields `client.undefined(nil)` whenever this target is generated without
|
|
49
|
+
// `go`, or before it in target order.
|
|
50
|
+
deriveEntityNames(entityMap)
|
|
43
51
|
const entityNames = Object.keys(entityMap).map(n => n.toLowerCase())
|
|
44
52
|
|
|
45
53
|
const FRAGMENT = Path.normalize(__dirname + '/../../../src/cmp/go-cli/fragment')
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import * as Path from 'node:path'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
-
cmp, each,
|
|
5
|
+
cmp, each, deriveEntityNames,
|
|
6
6
|
File, Content, Fragment, Slot,
|
|
7
7
|
} from '@voxgig/sdkgen'
|
|
8
8
|
|
|
@@ -54,6 +54,14 @@ const Main = cmp(function Main(props: any) {
|
|
|
54
54
|
const mcpModule = `github.com/${org}/${model.name}-sdk/go-mcp`
|
|
55
55
|
|
|
56
56
|
const entityMap: any = getModelPath(model, `main.${KIT}.entity`)
|
|
57
|
+
// Derive each entity's PascalCase `Name` here rather than relying on another
|
|
58
|
+
// TARGET having run first. This target sets `phase.entity: { active: false }`
|
|
59
|
+
// (see model/target/go-{cli,mcp}.aontu), so create-sdkgen's Root.ts never
|
|
60
|
+
// calls names() for it — the accessor emitted below used to read `Name` off
|
|
61
|
+
// objects the `go` target happened to have named as a side effect, which
|
|
62
|
+
// yields `client.undefined(nil)` whenever this target is generated without
|
|
63
|
+
// `go`, or before it in target order.
|
|
64
|
+
deriveEntityNames(entityMap)
|
|
57
65
|
const entityNames = Object.keys(entityMap).map(n => n.toLowerCase())
|
|
58
66
|
const entityHelp = entityNames.length > 0 ? entityNames.join(' | ') : '(none)'
|
|
59
67
|
|
|
@@ -67,35 +67,44 @@ readDoc path = do
|
|
|
67
67
|
pure (either (const "") id r)
|
|
68
68
|
|
|
69
69
|
|
|
70
|
-
-- Extract the body of every fenced Haskell block from markdown.
|
|
71
|
-
--
|
|
72
|
-
--
|
|
73
|
-
|
|
70
|
+
-- Extract the body of every fenced Haskell block from markdown. Returns
|
|
71
|
+
-- (blocks, allClosed) where allClosed is False when a fence was opened but
|
|
72
|
+
-- never closed before EOF — a malformed doc that must fail rather than pass
|
|
73
|
+
-- silently on the unterminated block. The fence marker (three backticks) is
|
|
74
|
+
-- built from its char code so this source stays backtick-free.
|
|
75
|
+
haskellBlocks :: String -> ([String], Bool)
|
|
74
76
|
haskellBlocks src = go (lines src)
|
|
75
77
|
where
|
|
76
78
|
fence3 = replicate 3 (toEnum 96 :: Char)
|
|
77
79
|
fenceHs = fence3 ++ "haskell"
|
|
78
|
-
go [] = []
|
|
80
|
+
go [] = ([], True)
|
|
79
81
|
go (l : ls)
|
|
80
82
|
| trimWs l == fenceHs =
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
case break (\\x -> trimWs x == fence3) ls of
|
|
84
|
+
(blk, []) -> ([unlines blk], False)
|
|
85
|
+
(blk, _ : rest) -> let (bs, ok) = go rest in (unlines blk : bs, ok)
|
|
83
86
|
| otherwise = go ls
|
|
84
87
|
|
|
85
88
|
|
|
86
|
-
-- Balanced (), [], {}
|
|
87
|
-
--
|
|
89
|
+
-- Balanced (), [], {} outside of double-quoted string literals, with each
|
|
90
|
+
-- closer matching the most-recent opener via a stack (so "([)]" is rejected,
|
|
91
|
+
-- not merely counted). The generated snippets never embed an escaped quote
|
|
92
|
+
-- inside a string.
|
|
88
93
|
balanced :: String -> Bool
|
|
89
|
-
balanced = go False
|
|
94
|
+
balanced = go False []
|
|
90
95
|
where
|
|
91
|
-
go inStr
|
|
92
|
-
go inStr
|
|
93
|
-
| c == '"' = go (not inStr)
|
|
94
|
-
| inStr = go inStr
|
|
95
|
-
| c == '('
|
|
96
|
+
go inStr stack [] = not inStr && null stack
|
|
97
|
+
go inStr stack (c : cs)
|
|
98
|
+
| c == '"' = go (not inStr) stack cs
|
|
99
|
+
| inStr = go inStr stack cs
|
|
100
|
+
| c == '(' = go inStr (')' : stack) cs
|
|
101
|
+
| c == '[' = go inStr (']' : stack) cs
|
|
102
|
+
| c == '{' = go inStr ('}' : stack) cs
|
|
96
103
|
| c == ')' || c == ']' || c == '}' =
|
|
97
|
-
|
|
98
|
-
|
|
104
|
+
case stack of
|
|
105
|
+
(top : rest) | top == c -> go inStr rest cs
|
|
106
|
+
_ -> False
|
|
107
|
+
| otherwise = go inStr stack cs
|
|
99
108
|
|
|
100
109
|
|
|
101
110
|
-- The three documents held to the gate, tagged by human label.
|
|
@@ -111,7 +120,8 @@ tests :: Counters -> IO ()
|
|
|
111
120
|
tests c =
|
|
112
121
|
forM_ docs (\\(label, path) -> do
|
|
113
122
|
src <- readDoc path
|
|
114
|
-
let blocks = haskellBlocks src
|
|
123
|
+
let (blocks, closed) = haskellBlocks src
|
|
124
|
+
runTest c (label ++ " haskell fences closed") (pure closed)
|
|
115
125
|
forM_ (zip [1 :: Int ..] blocks) (\\(i, blk) ->
|
|
116
126
|
runTest c (label ++ " haskell block " ++ show i ++ " well-formed")
|
|
117
127
|
(pure (not (null (trimWs blk)) && balanced blk))))
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
File, Content,
|
|
38
38
|
} from '@voxgig/sdkgen'
|
|
39
39
|
|
|
40
|
-
import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
|
|
40
|
+
import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
|
|
41
41
|
|
|
42
42
|
import {
|
|
43
43
|
KIT,
|
|
@@ -134,11 +134,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
|
|
|
134
134
|
// filter, so inactive entities still get class files referencing these
|
|
135
135
|
// typed names. Filter on `name` (always present), NOT `active` — parity
|
|
136
136
|
// with the go emitter's fix.
|
|
137
|
-
const entityList =
|
|
137
|
+
const entityList = deriveEntityNames(entity)
|
|
138
138
|
// Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
|
|
139
139
|
// entity not yet named (e.g. a fieldless placeholder) would otherwise read
|
|
140
140
|
// `Name = undefined` below. Parity with the go/py/csharp emitter's fix.
|
|
141
|
-
entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
|
|
142
141
|
|
|
143
142
|
// Surface duplicate generated type names (two entities with the same
|
|
144
143
|
// PascalCase Name) — they would redeclare a type in statically-typed
|
|
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
|
|
|
4
4
|
import {
|
|
5
5
|
cmp, camelify, names,
|
|
6
6
|
File, Content, Folder, Fragment,
|
|
7
|
-
entityClassName,
|
|
7
|
+
entityClassName, entityCollection,
|
|
8
8
|
} from '@voxgig/sdkgen'
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -25,7 +25,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
25
25
|
// Collision-free entity CLASS name (see entityClassName): normally
|
|
26
26
|
// `<Name>Entity`, but disambiguated (e.g. `<Name>EntityClient`) when it
|
|
27
27
|
// would clash with another entity's data-type name.
|
|
28
|
-
const entityColl =
|
|
28
|
+
const entityColl = entityCollection(model)
|
|
29
29
|
const cls = entityClassName(entity, entityColl)
|
|
30
30
|
|
|
31
31
|
const entrep = {
|
|
@@ -42,7 +42,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
42
42
|
|
|
43
43
|
File({ name: cls + '.' + target.ext }, () => {
|
|
44
44
|
|
|
45
|
-
const opnames = Object.keys(entity.op)
|
|
45
|
+
const opnames = Object.keys(entity.op || {})
|
|
46
46
|
|
|
47
47
|
// For each CRUD op: if the spec defines it, splice in the real
|
|
48
48
|
// implementation. Otherwise emit a stub that satisfies the SdkEntity
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
import { cmp, Content, entityClassName } from '@voxgig/sdkgen'
|
|
3
|
+
import { cmp, Content, entityClassName, entityCollection } from '@voxgig/sdkgen'
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
KIT,
|
|
@@ -18,7 +18,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
|
|
|
18
18
|
const { model } = props.ctx$
|
|
19
19
|
|
|
20
20
|
const javapackage = javaPackage(model)
|
|
21
|
-
const entityColl =
|
|
21
|
+
const entityColl = entityCollection(model)
|
|
22
22
|
const cls = entityClassName(entity, entityColl)
|
|
23
23
|
const accessor = javaVarName(entity.name)
|
|
24
24
|
|
|
@@ -31,7 +31,7 @@ const Package = cmp(async function Package(props: any) {
|
|
|
31
31
|
|
|
32
32
|
// Dep name convention: "groupId:artifactId" with an explicit version.
|
|
33
33
|
const deps: { groupId: string, artifactId: string, version: string, kind: string }[] = []
|
|
34
|
-
for (const d of collectDeps(model, target.name, target.deps)) {
|
|
34
|
+
for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
|
|
35
35
|
const [dgroup, dartifact] = String(d.name).split(':')
|
|
36
36
|
if (dgroup && dartifact && d.version) {
|
|
37
37
|
deps.push({
|
|
@@ -95,6 +95,23 @@ public class ReadmeExamplesTest {
|
|
|
95
95
|
return blocks;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
// A well-formed doc has an even number of triple-backtick fence markers:
|
|
99
|
+
// every opening fence has a matching close. An odd count means a fence was
|
|
100
|
+
// opened but never closed — a truncated or malformed generated doc. The
|
|
101
|
+
// split-based javaBlocks() would silently treat the unterminated tail as a
|
|
102
|
+
// complete block, so this must be checked explicitly.
|
|
103
|
+
private static boolean fencesClosed(String text) {
|
|
104
|
+
String fence = String.valueOf((char) 96).repeat(3);
|
|
105
|
+
int count = 0;
|
|
106
|
+
int from = 0;
|
|
107
|
+
int idx;
|
|
108
|
+
while ((idx = text.indexOf(fence, from)) >= 0) {
|
|
109
|
+
count++;
|
|
110
|
+
from = idx + fence.length();
|
|
111
|
+
}
|
|
112
|
+
return count % 2 == 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
98
115
|
private static String readDoc(Path path, String label) {
|
|
99
116
|
assertTrue(Files.exists(path), label + " not found: " + path);
|
|
100
117
|
try {
|
|
@@ -158,7 +175,12 @@ public class ReadmeExamplesTest {
|
|
|
158
175
|
if (!Files.exists(path)) {
|
|
159
176
|
continue;
|
|
160
177
|
}
|
|
161
|
-
|
|
178
|
+
String src = readDoc(path, doc[0]);
|
|
179
|
+
if (!fencesClosed(src)) {
|
|
180
|
+
failures.add(doc[0] +
|
|
181
|
+
" has an unclosed code fence (opened but never closed)");
|
|
182
|
+
}
|
|
183
|
+
List<String> blocks = javaBlocks(src);
|
|
162
184
|
int balancedCount = 0;
|
|
163
185
|
for (int i = 0; i < blocks.size(); i++) {
|
|
164
186
|
if (balanced(blocks.get(i))) {
|
|
@@ -75,7 +75,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
75
75
|
|
|
76
76
|
const authActive = isAuthActive(model)
|
|
77
77
|
|
|
78
|
-
const opnames = Object.keys(entity.op)
|
|
78
|
+
const opnames = Object.keys(entity.op || {})
|
|
79
79
|
const hasLoad = opnames.includes('load')
|
|
80
80
|
const hasList = opnames.includes('list')
|
|
81
81
|
|
|
@@ -83,8 +83,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
83
83
|
return
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const loadOp = entity.op
|
|
87
|
-
const listOp = entity.op
|
|
86
|
+
const loadOp = entity.op?.load
|
|
87
|
+
const listOp = entity.op?.list
|
|
88
88
|
|
|
89
89
|
// Get load point info
|
|
90
90
|
const loadPoint = loadOp?.points?.[0]
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
File, Content,
|
|
27
27
|
} from '@voxgig/sdkgen'
|
|
28
28
|
|
|
29
|
-
import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
|
|
29
|
+
import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
|
|
30
30
|
|
|
31
31
|
import {
|
|
32
32
|
KIT,
|
|
@@ -57,11 +57,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
|
|
|
57
57
|
// filter, so inactive entities still get class files referencing these
|
|
58
58
|
// typed names. Filter on `name` (always present), NOT `active` — parity
|
|
59
59
|
// with the go emitter's fix.
|
|
60
|
-
const entityList =
|
|
60
|
+
const entityList = deriveEntityNames(entity)
|
|
61
61
|
// Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
|
|
62
62
|
// entity not yet named (e.g. a fieldless placeholder) would otherwise read
|
|
63
63
|
// `Name = undefined` below. Parity with the go emitter's fix.
|
|
64
|
-
entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
|
|
65
64
|
|
|
66
65
|
// Surface duplicate generated type names (two entities with the same
|
|
67
66
|
// PascalCase Name) — they would redeclare a type in statically-typed
|
|
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
|
|
|
4
4
|
import {
|
|
5
5
|
cmp, each, camelify, names,
|
|
6
6
|
File, Content, Folder, Fragment, Line, FeatureHook, Slot,
|
|
7
|
-
entityClassName,
|
|
7
|
+
entityClassName, entityCollection,
|
|
8
8
|
} from '@voxgig/sdkgen'
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -22,7 +22,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
22
22
|
// Collision-free entity CLASS name (see entityClassName): normally
|
|
23
23
|
// `<Name>Entity`, disambiguated when it would clash with another entity's
|
|
24
24
|
// data-type name. The class file name and the Main require path both use this.
|
|
25
|
-
const entityColl =
|
|
25
|
+
const entityColl = entityCollection(model)
|
|
26
26
|
const cls = entityClassName(entity, entityColl)
|
|
27
27
|
|
|
28
28
|
const entrep = {
|
|
@@ -37,7 +37,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
37
37
|
|
|
38
38
|
File({ name: cls + '.' + target.name }, () => {
|
|
39
39
|
|
|
40
|
-
const opnames = Object.keys(entity.op)
|
|
40
|
+
const opnames = Object.keys(entity.op || {})
|
|
41
41
|
|
|
42
42
|
const opfrags =
|
|
43
43
|
(['load', 'list', 'create', 'update', 'remove']
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
|
-
import { cmp, Content, entityClassName } from '@voxgig/sdkgen'
|
|
3
|
+
import { cmp, Content, entityClassName, entityCollection } from '@voxgig/sdkgen'
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
KIT,
|
|
@@ -14,7 +14,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
|
|
|
14
14
|
|
|
15
15
|
// Return the collision-free class (entityClassName); the accessor METHOD name
|
|
16
16
|
// (entity.Name) is unchanged so callers still write client.<Name>().
|
|
17
|
-
const cls = entityClassName(entity,
|
|
17
|
+
const cls = entityClassName(entity, entityCollection(model))
|
|
18
18
|
|
|
19
19
|
Content(`
|
|
20
20
|
// Entity access: \`client.${entity.Name}().list()\` / \`client.${entity.Name}().load({ id })\`.
|
|
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
|
|
|
4
4
|
import {
|
|
5
5
|
cmp, each, names, cmap,
|
|
6
6
|
List, File, Content, Copy, Folder, Fragment, Line, FeatureHook,
|
|
7
|
-
entityClassName,
|
|
7
|
+
entityClassName, entityCollection,
|
|
8
8
|
} from '@voxgig/sdkgen'
|
|
9
9
|
|
|
10
10
|
|
|
@@ -57,7 +57,7 @@ const Main = cmp(async function Main(props: any) {
|
|
|
57
57
|
Line(`// ${model.const.Name} ${target.Name} SDK\n`)
|
|
58
58
|
|
|
59
59
|
List({ item: entity }, ({ item }: any) => {
|
|
60
|
-
const cls = entityClassName(item,
|
|
60
|
+
const cls = entityClassName(item, entityCollection(model))
|
|
61
61
|
return Line(`const { ${cls} } = require('./entity/${cls}')`)
|
|
62
62
|
})
|
|
63
63
|
|
|
@@ -48,7 +48,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
48
48
|
apikey: env.${PROJECTNAME}_APIKEY,`
|
|
49
49
|
: ''
|
|
50
50
|
|
|
51
|
-
const opnames = Object.keys(entity.op)
|
|
51
|
+
const opnames = Object.keys(entity.op || {})
|
|
52
52
|
const hasLoad = opnames.includes('load')
|
|
53
53
|
const hasList = opnames.includes('list')
|
|
54
54
|
|
|
@@ -134,7 +134,7 @@ function directSetup(mockres) {
|
|
|
134
134
|
|
|
135
135
|
|
|
136
136
|
function generateDirectLoad(model: Model, entity: ModelEntity) {
|
|
137
|
-
const loadOp = entity.op
|
|
137
|
+
const loadOp = entity.op?.load
|
|
138
138
|
const loadPoint: ModelPoint | undefined = loadOp?.points?.[0]
|
|
139
139
|
|
|
140
140
|
if (null == loadPoint) {
|
|
@@ -145,7 +145,7 @@ function generateDirectLoad(model: Model, entity: ModelEntity) {
|
|
|
145
145
|
const loadPath = normalizePathParams(loadPoint.parts || [], loadParams, loadPoint.rename?.param)
|
|
146
146
|
|
|
147
147
|
// Get list info for live mode bootstrapping
|
|
148
|
-
const listOp = entity.op
|
|
148
|
+
const listOp = entity.op?.list
|
|
149
149
|
const listPoint = listOp?.points?.[0]
|
|
150
150
|
const listParams = listPoint?.args?.params || []
|
|
151
151
|
const listPath = listPoint ? normalizePathParams(listPoint.parts || [], listParams, listPoint.rename?.param) : ''
|
|
@@ -231,7 +231,7 @@ ${paramAsserts} }
|
|
|
231
231
|
|
|
232
232
|
|
|
233
233
|
function generateDirectList(model: Model, entity: ModelEntity) {
|
|
234
|
-
const listOp = entity.op
|
|
234
|
+
const listOp = entity.op?.list
|
|
235
235
|
const listPoint: ModelPoint | undefined = listOp?.points?.[0]
|
|
236
236
|
|
|
237
237
|
if (null == listPoint) {
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
File, Content,
|
|
40
40
|
} from '@voxgig/sdkgen'
|
|
41
41
|
|
|
42
|
-
import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions } from '@voxgig/sdkgen'
|
|
42
|
+
import { canonToType, opTypeName, opRequestShape, warnEntityTypeCollisions , deriveEntityNames } from '@voxgig/sdkgen'
|
|
43
43
|
|
|
44
44
|
import {
|
|
45
45
|
KIT,
|
|
@@ -132,11 +132,10 @@ const EntityTypes = cmp(function EntityTypes(props: any) {
|
|
|
132
132
|
// filter, so inactive entities still get class files referencing these
|
|
133
133
|
// typed names. Filter on `name` (always present), NOT `active` — parity
|
|
134
134
|
// with the go emitter's fix.
|
|
135
|
-
const entityList =
|
|
135
|
+
const entityList = deriveEntityNames(entity)
|
|
136
136
|
// Derive the PascalCase Name up-front — it is set LAZILY by names(), so an
|
|
137
137
|
// entity not yet named (e.g. a fieldless placeholder) would otherwise read
|
|
138
138
|
// `Name = undefined` below. Parity with the go/py/csharp/java emitter's fix.
|
|
139
|
-
entityList.forEach((e: any) => { if (null == e.Name) names(e, e.name) })
|
|
140
139
|
|
|
141
140
|
// Surface duplicate generated type names (two entities with the same
|
|
142
141
|
// PascalCase Name) — they would redeclare a type in statically-typed
|
|
@@ -4,7 +4,7 @@ import * as Path from 'node:path'
|
|
|
4
4
|
import {
|
|
5
5
|
cmp, camelify, names,
|
|
6
6
|
File, Content, Folder, Fragment,
|
|
7
|
-
entityClassName,
|
|
7
|
+
entityClassName, entityCollection,
|
|
8
8
|
} from '@voxgig/sdkgen'
|
|
9
9
|
|
|
10
10
|
import {
|
|
@@ -25,7 +25,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
25
25
|
// Collision-free entity CLASS name (see entityClassName): normally
|
|
26
26
|
// `<Name>Entity`, but disambiguated (e.g. `<Name>EntityClient`) when it
|
|
27
27
|
// would clash with another entity's data-type name.
|
|
28
|
-
const entityColl =
|
|
28
|
+
const entityColl = entityCollection(model)
|
|
29
29
|
const cls = entityClassName(entity, entityColl)
|
|
30
30
|
|
|
31
31
|
const entrep = {
|
|
@@ -41,7 +41,7 @@ const Entity = cmp(function Entity(props: any) {
|
|
|
41
41
|
|
|
42
42
|
File({ name: cls + '.' + target.ext }, () => {
|
|
43
43
|
|
|
44
|
-
const opnames = Object.keys(entity.op)
|
|
44
|
+
const opnames = Object.keys(entity.op || {})
|
|
45
45
|
|
|
46
46
|
// For each CRUD op: if the spec defines it, splice in the real
|
|
47
47
|
// implementation. Otherwise emit a stub that satisfies the SdkEntity
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import { cmp, Content, entityClassName } from '@voxgig/sdkgen'
|
|
2
|
+
import { cmp, Content, entityClassName, entityCollection } from '@voxgig/sdkgen'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
KIT,
|
|
@@ -17,7 +17,7 @@ const MainEntity = cmp(async function MainEntity(props: any) {
|
|
|
17
17
|
const { model } = props.ctx$
|
|
18
18
|
|
|
19
19
|
const kotlinpackage = kotlinPackage(model)
|
|
20
|
-
const entityColl =
|
|
20
|
+
const entityColl = entityCollection(model)
|
|
21
21
|
const cls = entityClassName(entity, entityColl)
|
|
22
22
|
const accessor = kotlinVarName(entity.name)
|
|
23
23
|
|
|
@@ -30,7 +30,7 @@ const Package = cmp(async function Package(props: any) {
|
|
|
30
30
|
|
|
31
31
|
// Dep name convention: "group:artifact" with an explicit version.
|
|
32
32
|
const deps: { group: string, artifact: string, version: string, kind: string }[] = []
|
|
33
|
-
for (const d of collectDeps(model, target.name, target.deps)) {
|
|
33
|
+
for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
|
|
34
34
|
const [dgroup, dartifact] = String(d.name).split(':')
|
|
35
35
|
if (dgroup && dartifact && d.version) {
|
|
36
36
|
deps.push({
|
|
@@ -46,7 +46,9 @@ const ReadmeExamplesTest = cmp(function ReadmeExamplesTest(props: any) {
|
|
|
46
46
|
// Checks, PER DOCUMENT:
|
|
47
47
|
// 1. PRESENCE: the doc exists and holds at least one kotlin block.
|
|
48
48
|
// 2. STRUCTURE: every kotlin block's braces / parens / brackets balance, so a
|
|
49
|
-
// truncated or malformed generated snippet fails loudly.
|
|
49
|
+
// truncated or malformed generated snippet fails loudly. Every code fence
|
|
50
|
+
// is also terminated (even fence count), so an unclosed fence at EOF is
|
|
51
|
+
// caught rather than accepted as a complete block.
|
|
50
52
|
//
|
|
51
53
|
// GENERATED by @voxgig/sdkgen — do not edit by hand.
|
|
52
54
|
|
|
@@ -97,6 +99,22 @@ class ReadmeExamplesTest {
|
|
|
97
99
|
return Files.readString(path)
|
|
98
100
|
}
|
|
99
101
|
|
|
102
|
+
// Count triple-backtick fence markers. A well-formed document opens and
|
|
103
|
+
// closes every fenced block, so the total must be EVEN. An odd count means a
|
|
104
|
+
// fence was opened but never closed (a truncated / malformed doc) — the
|
|
105
|
+
// split-based extraction in kotlinBlocks() would otherwise accept the
|
|
106
|
+
// unterminated trailing segment as a complete block and pass silently.
|
|
107
|
+
private fun fenceCount(text: String): Int {
|
|
108
|
+
val fence = 96.toChar().toString().repeat(3)
|
|
109
|
+
var count = 0
|
|
110
|
+
var idx = text.indexOf(fence)
|
|
111
|
+
while (idx >= 0) {
|
|
112
|
+
count++
|
|
113
|
+
idx = text.indexOf(fence, idx + fence.length)
|
|
114
|
+
}
|
|
115
|
+
return count
|
|
116
|
+
}
|
|
117
|
+
|
|
100
118
|
// True when every open/close delimiter balances (ignores the subtlety of
|
|
101
119
|
// delimiters inside string literals — a generated snippet never nests an
|
|
102
120
|
// unbalanced delimiter in a string, and this catches truncation).
|
|
@@ -145,7 +163,14 @@ class ReadmeExamplesTest {
|
|
|
145
163
|
if (!Files.exists(path)) {
|
|
146
164
|
continue
|
|
147
165
|
}
|
|
148
|
-
val
|
|
166
|
+
val src = readDoc(path, doc[0])
|
|
167
|
+
// A fence opened but never closed is a malformed doc — fail loudly
|
|
168
|
+
// rather than silently accepting the unterminated trailing segment as a
|
|
169
|
+
// complete block.
|
|
170
|
+
if (fenceCount(src) % 2 != 0) {
|
|
171
|
+
failures.add(doc[0] + " has an unterminated code fence")
|
|
172
|
+
}
|
|
173
|
+
val blocks = kotlinBlocks(src)
|
|
149
174
|
var balancedCount = 0
|
|
150
175
|
for (i in blocks.indices) {
|
|
151
176
|
if (balanced(blocks[i])) {
|
|
@@ -72,7 +72,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
72
72
|
|
|
73
73
|
const authActive = isAuthActive(model)
|
|
74
74
|
|
|
75
|
-
const opnames = Object.keys(entity.op)
|
|
75
|
+
const opnames = Object.keys(entity.op || {})
|
|
76
76
|
const hasLoad = opnames.includes('load')
|
|
77
77
|
const hasList = opnames.includes('list')
|
|
78
78
|
|
|
@@ -80,8 +80,8 @@ const TestDirect = cmp(function TestDirect(props: any) {
|
|
|
80
80
|
return
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const loadOp = entity.op
|
|
84
|
-
const listOp = entity.op
|
|
83
|
+
const loadOp = entity.op?.load
|
|
84
|
+
const listOp = entity.op?.list
|
|
85
85
|
|
|
86
86
|
const loadPoint = loadOp?.points?.[0]
|
|
87
87
|
const loadPath = loadPoint ? normalizePathParams(loadPoint.parts || [], loadPoint?.args?.params || [], loadPoint?.rename?.param) : ''
|