@shortlink-org/portolan 0.1.0 → 0.2.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 +69 -205
- package/cli/init.mjs +285 -0
- package/cli/init.test.mjs +158 -0
- package/cli/portolan.mjs +37 -73
- package/index.html +19 -0
- package/package.json +5 -10
- package/plugins/README.md +66 -27
- package/plugins/portolan-go.wasm +0 -0
- package/portolan.json +52 -73
- package/public/landing/cats/bug-hunter.webp +0 -0
- package/public/landing/cats/compass-nap.webp +0 -0
- package/public/landing/cats/diagrammer.webp +0 -0
- package/public/landing/cats/docs-reader.webp +0 -0
- package/public/landing/cats/laptop.webp +0 -0
- package/public/landing/cats/map-inspector.webp +0 -0
- package/public/landing/cats/server-break.webp +0 -0
- package/public/landing/cats/star-mapper.webp +0 -0
- package/public/landing/cats/system-builder.webp +0 -0
- package/public/landing/cats/thread-tangle.webp +0 -0
- package/public/og.png +0 -0
- package/public/readme-header.webp +0 -0
- package/schema/portolan.schema.json +180 -0
- package/scripts/builtin-plugins.mjs +14 -0
- package/scripts/delivery-presets.mjs +353 -0
- package/scripts/gen.mjs +37 -4
- package/scripts/history.mjs +126 -0
- package/scripts/history.test.mjs +110 -0
- package/scripts/host-plugins/fetch-bsr.mjs +351 -0
- package/{plugins/fetch-bsr/options.schema.json → scripts/host-plugins/fetch-bsr.options.json} +4 -0
- package/scripts/host-plugins/fetch-bsr.test.mjs +212 -0
- package/scripts/host-plugins/fetch-csr.mjs +386 -0
- package/scripts/host-plugins/fetch-csr.test.mjs +178 -0
- package/scripts/host-plugins/fetch-git.mjs +379 -0
- package/scripts/host-plugins/fetch-git.test.mjs +210 -0
- package/scripts/local-api.mjs +22 -4
- package/scripts/local-api.test.mjs +84 -2
- package/scripts/manifest.mjs +3 -2
- package/scripts/package-smoke.mjs +6 -2
- package/scripts/plugin-host.mjs +77 -18
- package/scripts/plugin-host.test.mjs +50 -1
- package/scripts/plugin-wasm-worker.mjs +6 -3
- package/scripts/run-builtin.mjs +6 -26
- package/scripts/schema.mjs +6 -1
- package/src/app/App.tsx +24 -429
- package/src/app/Breadcrumbs.test.ts +7 -0
- package/src/app/Breadcrumbs.tsx +15 -2
- package/src/app/CatalogApp.tsx +436 -0
- package/src/app/Sidebar.tsx +0 -1
- package/src/chat/ChatPanel.tsx +98 -41
- package/src/chat/Composer.tsx +4 -2
- package/src/chat/Conversation.tsx +79 -15
- package/src/chat/Header.tsx +43 -9
- package/src/chat/Starter.tsx +64 -3
- package/src/chat/Steps.tsx +1 -1
- package/src/chat/flags.test.ts +51 -14
- package/src/chat/flags.ts +8 -2
- package/src/chat/page-context.test.ts +51 -0
- package/src/chat/page-context.ts +128 -0
- package/src/chat/prompt.test.ts +49 -1
- package/src/chat/prompt.ts +42 -1
- package/src/chat/tools.ts +7 -2
- package/src/chat/transport.ts +16 -4
- package/src/components/ProblemRow.tsx +182 -0
- package/src/data.ts +20 -5
- package/src/er/ErCanvas.tsx +1 -1
- package/src/graph/DependencyGraph.tsx +10 -1
- package/src/graph/FocusedEventGraph.tsx +1 -1
- package/src/graph/GraphToolbar.tsx +2 -4
- package/src/index.css +220 -2
- package/src/landing/DraggableReveal.tsx +168 -0
- package/src/landing/EstateGraph.tsx +28 -0
- package/src/landing/FlowPlayback.tsx +282 -0
- package/src/landing/HeroMap.tsx +128 -0
- package/src/landing/LandingPage.tsx +757 -0
- package/src/landing/ProductFrame.tsx +68 -0
- package/src/landing/ProductTour.tsx +371 -0
- package/src/landing/catalog.ts +25 -0
- package/src/landing/motion.tsx +52 -0
- package/src/lib/all-problems.ts +28 -0
- package/src/lib/local-api.ts +35 -1
- package/src/lib/setup-info.test.ts +8 -0
- package/src/lib/setup-info.ts +3 -2
- package/src/lib/source-code.ts +2 -1
- package/src/likec4/C4View.tsx +5 -0
- package/src/likec4/InteractiveView.tsx +5 -1
- package/src/map/ContextMapGraph.tsx +47 -20
- package/src/pages/ContextMap.tsx +5 -1
- package/src/pages/FlowDetail.tsx +24 -7
- package/src/pages/Overview.tsx +68 -12
- package/src/pages/Problems.tsx +6 -193
- package/src/pages/Settings.tsx +133 -108
- package/src/pages/settings/DeliverySettings.tsx +229 -0
- package/src/pages/settings/PreferencesSettings.tsx +101 -0
- package/src/routes.test.ts +20 -0
- package/src/routes.ts +13 -1
- package/vite.config.ts +12 -8
- package/catalog/enum_test.go +0 -46
- package/catalog/model.go +0 -932
- package/catalog/roundtrip_test.go +0 -185
- package/catalog/via_test.go +0 -38
- package/go.mod +0 -14
- package/go.sum +0 -14
- package/internal/commands/cargo.go +0 -154
- package/internal/commands/commands.go +0 -143
- package/internal/commands/commands_test.go +0 -323
- package/internal/commands/gradle.go +0 -77
- package/internal/commands/justfile.go +0 -82
- package/internal/commands/makefile.go +0 -106
- package/internal/commands/maven.go +0 -93
- package/internal/commands/packagejson.go +0 -159
- package/internal/commands/pyproject.go +0 -169
- package/internal/commands/taskfile.go +0 -101
- package/internal/commands/testdata/estate/.cargo/config.toml +0 -7
- package/internal/commands/testdata/estate/Makefile +0 -33
- package/internal/commands/testdata/estate/Taskfile.yml +0 -21
- package/internal/commands/testdata/estate/build.gradle.kts +0 -21
- package/internal/commands/testdata/estate/justfile +0 -20
- package/internal/commands/testdata/estate/package.json +0 -13
- package/internal/commands/testdata/estate/pom.xml +0 -29
- package/internal/commands/testdata/estate/pyproject.toml +0 -27
- package/internal/commands/testdata/estate/xtask/src/main.rs +0 -27
- package/internal/commands/testdata/golden/commands.json +0 -305
- package/internal/gohttp/analyze.go +0 -2500
- package/internal/gohttp/endpoints.go +0 -1067
- package/internal/gohttp/roots.go +0 -320
- package/internal/gohttp/typed.go +0 -143
- package/internal/goscan/constants.go +0 -85
- package/internal/goscan/goscan_test.go +0 -227
- package/internal/goscan/names.go +0 -52
- package/internal/goscan/parse_test.go +0 -11
- package/internal/goscan/source.go +0 -36
- package/internal/goscan/tree.go +0 -155
- package/internal/goscan/types.go +0 -99
- package/internal/wsdl/ids.go +0 -127
- package/internal/wsdl/ids_test.go +0 -21
- package/internal/wsdl/model.go +0 -62
- package/internal/wsdl/parse.go +0 -920
- package/internal/wsdl/parse_test.go +0 -133
- package/plugin/describe.go +0 -107
- package/plugin/describe_test.go +0 -114
- package/plugin/protocol.go +0 -117
- package/plugin/schematest/schematest.go +0 -126
- package/plugins/extract-adr/describe.go +0 -19
- package/plugins/extract-adr/describe_test.go +0 -11
- package/plugins/extract-adr/extract.go +0 -153
- package/plugins/extract-adr/extract_test.go +0 -350
- package/plugins/extract-adr/history.go +0 -99
- package/plugins/extract-adr/main.go +0 -65
- package/plugins/extract-adr/parse.go +0 -642
- package/plugins/extract-adr/parse_test.go +0 -405
- package/plugins/extract-asyncapi/describe.go +0 -19
- package/plugins/extract-asyncapi/describe_test.go +0 -11
- package/plugins/extract-asyncapi/extract.go +0 -315
- package/plugins/extract-asyncapi/extract_test.go +0 -197
- package/plugins/extract-asyncapi/main.go +0 -48
- package/plugins/extract-asyncapi/spec.go +0 -151
- package/plugins/extract-commands/describe.go +0 -19
- package/plugins/extract-commands/describe_test.go +0 -11
- package/plugins/extract-commands/extract.go +0 -79
- package/plugins/extract-commands/extract_test.go +0 -92
- package/plugins/extract-commands/main.go +0 -55
- package/plugins/extract-csr/avro.go +0 -258
- package/plugins/extract-csr/describe.go +0 -19
- package/plugins/extract-csr/describe_test.go +0 -11
- package/plugins/extract-csr/extract.go +0 -338
- package/plugins/extract-csr/extract_test.go +0 -374
- package/plugins/extract-csr/jsonschema.go +0 -343
- package/plugins/extract-csr/lock.go +0 -25
- package/plugins/extract-csr/main.go +0 -84
- package/plugins/extract-csr/subject.go +0 -102
- package/plugins/extract-flows/describe.go +0 -19
- package/plugins/extract-flows/describe_test.go +0 -11
- package/plugins/extract-flows/extract.go +0 -91
- package/plugins/extract-flows/main.go +0 -45
- package/plugins/extract-flows/parse.go +0 -593
- package/plugins/extract-flows/parse_test.go +0 -204
- package/plugins/extract-glossary/describe.go +0 -19
- package/plugins/extract-glossary/describe_test.go +0 -11
- package/plugins/extract-glossary/extract.go +0 -115
- package/plugins/extract-glossary/extract_test.go +0 -220
- package/plugins/extract-glossary/main.go +0 -59
- package/plugins/extract-glossary/parse.go +0 -214
- package/plugins/extract-glossary/parse_test.go +0 -203
- package/plugins/extract-go/aggregate.go +0 -214
- package/plugins/extract-go/client.go +0 -409
- package/plugins/extract-go/client_test.go +0 -266
- package/plugins/extract-go/describe.go +0 -19
- package/plugins/extract-go/describe_test.go +0 -11
- package/plugins/extract-go/enum.go +0 -195
- package/plugins/extract-go/enum_test.go +0 -82
- package/plugins/extract-go/event.go +0 -99
- package/plugins/extract-go/extract.go +0 -191
- package/plugins/extract-go/extract_test.go +0 -261
- package/plugins/extract-go/flow.go +0 -1441
- package/plugins/extract-go/flow_test.go +0 -609
- package/plugins/extract-go/httpclient.go +0 -174
- package/plugins/extract-go/httpclient_test.go +0 -296
- package/plugins/extract-go/ids.go +0 -92
- package/plugins/extract-go/layout.go +0 -225
- package/plugins/extract-go/layout_test.go +0 -125
- package/plugins/extract-go/lifecycle.go +0 -324
- package/plugins/extract-go/lifecycle_test.go +0 -108
- package/plugins/extract-go/main.go +0 -79
- package/plugins/extract-go/operation.go +0 -157
- package/plugins/extract-go/source.go +0 -316
- package/plugins/extract-go/transport.go +0 -321
- package/plugins/extract-go/transport_test.go +0 -145
- package/plugins/extract-go/wiring.go +0 -434
- package/plugins/extract-go-nats/describe.go +0 -19
- package/plugins/extract-go-nats/describe_test.go +0 -11
- package/plugins/extract-go-nats/extract.go +0 -177
- package/plugins/extract-go-nats/extract_test.go +0 -389
- package/plugins/extract-go-nats/index.go +0 -379
- package/plugins/extract-go-nats/main.go +0 -42
- package/plugins/extract-go-nats/resolve.go +0 -161
- package/plugins/extract-go-nats/sites.go +0 -224
- package/plugins/extract-graphql/describe.go +0 -19
- package/plugins/extract-graphql/describe_test.go +0 -11
- package/plugins/extract-graphql/extract.go +0 -433
- package/plugins/extract-graphql/extract_test.go +0 -256
- package/plugins/extract-graphql/ids.go +0 -49
- package/plugins/extract-graphql/lex.go +0 -237
- package/plugins/extract-graphql/main.go +0 -51
- package/plugins/extract-graphql/parse.go +0 -621
- package/plugins/extract-graphql/parse_test.go +0 -122
- package/plugins/extract-http-clients/describe.go +0 -19
- package/plugins/extract-http-clients/describe_test.go +0 -11
- package/plugins/extract-http-clients/extract.go +0 -705
- package/plugins/extract-http-clients/extract_test.go +0 -1263
- package/plugins/extract-http-clients/main.go +0 -42
- package/plugins/extract-openapi/describe.go +0 -19
- package/plugins/extract-openapi/describe_test.go +0 -11
- package/plugins/extract-openapi/discover.go +0 -243
- package/plugins/extract-openapi/extract.go +0 -526
- package/plugins/extract-openapi/extract_test.go +0 -545
- package/plugins/extract-openapi/main.go +0 -75
- package/plugins/extract-openapi/spec.go +0 -350
- package/plugins/extract-project/describe.go +0 -19
- package/plugins/extract-project/describe_test.go +0 -11
- package/plugins/extract-project/extract.go +0 -221
- package/plugins/extract-project/extract_test.go +0 -109
- package/plugins/extract-project/main.go +0 -41
- package/plugins/extract-proto/ast.go +0 -125
- package/plugins/extract-proto/consumes.go +0 -77
- package/plugins/extract-proto/describe.go +0 -19
- package/plugins/extract-proto/extract.go +0 -293
- package/plugins/extract-proto/extract_test.go +0 -459
- package/plugins/extract-proto/ids.go +0 -89
- package/plugins/extract-proto/ids_test.go +0 -57
- package/plugins/extract-proto/lex.go +0 -285
- package/plugins/extract-proto/main.go +0 -100
- package/plugins/extract-proto/module.go +0 -120
- package/plugins/extract-proto/parse.go +0 -720
- package/plugins/extract-proto/parse_test.go +0 -307
- package/plugins/extract-proto/provides.go +0 -236
- package/plugins/extract-proto/resolve.go +0 -222
- package/plugins/extract-proto/resolve_test.go +0 -119
- package/plugins/extract-redis/describe.go +0 -19
- package/plugins/extract-redis/describe_test.go +0 -11
- package/plugins/extract-redis/extract.go +0 -183
- package/plugins/extract-redis/extract_test.go +0 -168
- package/plugins/extract-redis/keyspaces.go +0 -469
- package/plugins/extract-redis/main.go +0 -44
- package/plugins/extract-river/describe.go +0 -19
- package/plugins/extract-river/describe_test.go +0 -11
- package/plugins/extract-river/extract.go +0 -507
- package/plugins/extract-river/extract_test.go +0 -132
- package/plugins/extract-river/main.go +0 -42
- package/plugins/extract-sql/ddl.go +0 -893
- package/plugins/extract-sql/ddl_test.go +0 -401
- package/plugins/extract-sql/describe.go +0 -19
- package/plugins/extract-sql/describe_test.go +0 -11
- package/plugins/extract-sql/layout.go +0 -221
- package/plugins/extract-sql/layout_test.go +0 -55
- package/plugins/extract-sql/lineage.go +0 -107
- package/plugins/extract-sql/main.go +0 -141
- package/plugins/extract-sql/maps.go +0 -564
- package/plugins/extract-sql/maps_java.go +0 -117
- package/plugins/extract-sql/maps_rust.go +0 -333
- package/plugins/extract-sql/maps_rust_test.go +0 -70
- package/plugins/extract-sql/maps_test.go +0 -204
- package/plugins/extract-sql/maps_ts.go +0 -398
- package/plugins/extract-sql/maps_ts_test.go +0 -136
- package/plugins/extract-sql/projection.go +0 -70
- package/plugins/extract-sql/projection_test.go +0 -50
- package/plugins/extract-sql/store.go +0 -420
- package/plugins/extract-sql/store_test.go +0 -233
- package/plugins/extract-sql/view.go +0 -295
- package/plugins/extract-watermill/describe.go +0 -19
- package/plugins/extract-watermill/describe_test.go +0 -11
- package/plugins/extract-watermill/extract.go +0 -1228
- package/plugins/extract-watermill/extract_test.go +0 -234
- package/plugins/extract-watermill/main.go +0 -41
- package/plugins/extract-wsdl/describe.go +0 -19
- package/plugins/extract-wsdl/describe_test.go +0 -11
- package/plugins/extract-wsdl/extract.go +0 -148
- package/plugins/extract-wsdl/extract_test.go +0 -45
- package/plugins/extract-wsdl/main.go +0 -53
- package/plugins/fetch-bsr/auth.go +0 -114
- package/plugins/fetch-bsr/bsr.go +0 -240
- package/plugins/fetch-bsr/cache.go +0 -66
- package/plugins/fetch-bsr/describe.go +0 -19
- package/plugins/fetch-bsr/fetch.go +0 -192
- package/plugins/fetch-bsr/fetch_test.go +0 -465
- package/plugins/fetch-bsr/lock.go +0 -71
- package/plugins/fetch-bsr/main.go +0 -117
- package/plugins/fetch-csr/auth.go +0 -95
- package/plugins/fetch-csr/cache.go +0 -69
- package/plugins/fetch-csr/describe.go +0 -19
- package/plugins/fetch-csr/describe_test.go +0 -11
- package/plugins/fetch-csr/fetch.go +0 -254
- package/plugins/fetch-csr/fetch_test.go +0 -494
- package/plugins/fetch-csr/lock.go +0 -95
- package/plugins/fetch-csr/main.go +0 -122
- package/plugins/fetch-csr/registry.go +0 -209
- package/plugins/fetch-git/cache.go +0 -65
- package/plugins/fetch-git/describe.go +0 -19
- package/plugins/fetch-git/describe_test.go +0 -11
- package/plugins/fetch-git/fetch.go +0 -181
- package/plugins/fetch-git/fetch_test.go +0 -332
- package/plugins/fetch-git/git.go +0 -169
- package/plugins/fetch-git/lock.go +0 -72
- package/plugins/fetch-git/main.go +0 -127
- package/plugins/fetch-git/offline.go +0 -39
- package/plugins/fetch-git/pin.go +0 -90
- package/plugins/fetch-git/pin_test.go +0 -74
- package/plugins/gen-backstage/describe.go +0 -17
- package/plugins/gen-backstage/main.go +0 -22
- package/plugins/gen-backstage/plugin.go +0 -473
- package/plugins/gen-backstage/plugin_test.go +0 -145
- package/plugins/gen-backstage.wasm +0 -0
- package/plugins/gen-markdown/adr.go +0 -162
- package/plugins/gen-markdown/aggregate.go +0 -362
- package/plugins/gen-markdown/canonical.go +0 -93
- package/plugins/gen-markdown/context.go +0 -90
- package/plugins/gen-markdown/coverage_test.go +0 -89
- package/plugins/gen-markdown/describe.go +0 -19
- package/plugins/gen-markdown/describe_test.go +0 -11
- package/plugins/gen-markdown/external.go +0 -71
- package/plugins/gen-markdown/flow.go +0 -277
- package/plugins/gen-markdown/glossary.go +0 -76
- package/plugins/gen-markdown/glossary_test.go +0 -148
- package/plugins/gen-markdown/llms.go +0 -302
- package/plugins/gen-markdown/main.go +0 -22
- package/plugins/gen-markdown/markdown.go +0 -254
- package/plugins/gen-markdown/markdown_test.go +0 -100
- package/plugins/gen-markdown/module.go +0 -107
- package/plugins/gen-markdown/plugin.go +0 -39
- package/plugins/gen-markdown/quality_test.go +0 -176
- package/plugins/gen-markdown/redis_test.go +0 -38
- package/plugins/gen-markdown/render.go +0 -344
- package/plugins/gen-markdown/render_test.go +0 -186
- package/plugins/gen-markdown/service.go +0 -456
- package/plugins/gen-markdown/source.go +0 -177
- package/plugins/gen-markdown/store.go +0 -201
- package/plugins/gen-markdown.wasm +0 -0
- package/plugins/gen-mermaid/describe.go +0 -17
- package/plugins/gen-mermaid/main.go +0 -22
- package/plugins/gen-mermaid/plugin.go +0 -104
- package/plugins/gen-mermaid/plugin_test.go +0 -33
- package/plugins/gen-mermaid.wasm +0 -0
- package/plugins/openapi/ids.go +0 -261
- package/plugins/openapi/ids_test.go +0 -98
- package/plugins/verify-codeowners/describe.go +0 -19
- package/plugins/verify-codeowners/describe_test.go +0 -11
- package/plugins/verify-codeowners/main.go +0 -75
- package/plugins/verify-codeowners/match.go +0 -85
- package/plugins/verify-codeowners/match_test.go +0 -47
- package/plugins/verify-codeowners/owners.go +0 -164
- package/plugins/verify-codeowners/owners_test.go +0 -225
- package/plugins/verify-codeowners/parse.go +0 -90
- package/plugins/verify-codeowners/parse_test.go +0 -62
- package/plugins/verify-otel/describe.go +0 -19
- package/plugins/verify-otel/describe_test.go +0 -11
- package/plugins/verify-otel/main.go +0 -53
- package/plugins/verify-otel/match.go +0 -336
- package/plugins/verify-otel/otlp.go +0 -200
- package/plugins/verify-otel/verify.go +0 -734
- package/plugins/verify-otel/verify_test.go +0 -460
- /package/{plugins/fetch-csr/options.schema.json → scripts/host-plugins/fetch-csr.options.json} +0 -0
- /package/{plugins/fetch-git/options.schema.json → scripts/host-plugins/fetch-git.options.json} +0 -0
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"io/fs"
|
|
5
|
-
"path"
|
|
6
|
-
"path/filepath"
|
|
7
|
-
"sort"
|
|
8
|
-
"strings"
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
// sourceLayout is the small amount of filesystem knowledge the extractor
|
|
12
|
-
// needs. Discovery is deliberately separate from reading behaviour: the rest
|
|
13
|
-
// of the extractor deals in aggregate/use-case keys and concrete package
|
|
14
|
-
// directories, not in one repository-wide directory convention.
|
|
15
|
-
type sourceLayout struct {
|
|
16
|
-
packages []string
|
|
17
|
-
domains map[string]string
|
|
18
|
-
useCases map[string]string
|
|
19
|
-
integrationEvents map[string]string
|
|
20
|
-
policies []string
|
|
21
|
-
http []string
|
|
22
|
-
grpc []string
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
func discoverLayout(root string) sourceLayout {
|
|
26
|
-
layout := sourceLayout{
|
|
27
|
-
packages: goPackageDirs(root, "internal"),
|
|
28
|
-
domains: map[string]string{},
|
|
29
|
-
useCases: map[string]string{},
|
|
30
|
-
integrationEvents: map[string]string{},
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
for _, dir := range layout.packages {
|
|
34
|
-
parts := strings.Split(filepath.ToSlash(dir), "/")
|
|
35
|
-
|
|
36
|
-
if aggregate, ok := domainDir(parts); ok {
|
|
37
|
-
layout.domains[aggregate] = dir
|
|
38
|
-
}
|
|
39
|
-
if aggregate, ok := integrationEventDir(parts); ok {
|
|
40
|
-
layout.integrationEvents[aggregate] = dir
|
|
41
|
-
}
|
|
42
|
-
if aggregate, name, ok := useCaseDir(parts); ok {
|
|
43
|
-
pkg, err := parsePkg(root, dir)
|
|
44
|
-
if err == nil && hasStructNamed(pkg, "UseCase") {
|
|
45
|
-
layout.useCases[aggregate+"/"+name] = dir
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if path.Base(dir) == "policy" {
|
|
50
|
-
layout.policies = append(layout.policies, dir)
|
|
51
|
-
}
|
|
52
|
-
if isTransportPackage(parts, "http") {
|
|
53
|
-
layout.http = append(layout.http, dir)
|
|
54
|
-
}
|
|
55
|
-
if isTransportPackage(parts, "grpc") {
|
|
56
|
-
layout.grpc = append(layout.grpc, dir)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
sort.Strings(layout.policies)
|
|
61
|
-
sort.Strings(layout.http)
|
|
62
|
-
sort.Strings(layout.grpc)
|
|
63
|
-
|
|
64
|
-
return layout
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
func hasStructNamed(pkg *pkg, name string) bool {
|
|
68
|
-
for _, decl := range pkg.structs() {
|
|
69
|
-
if decl.name == name {
|
|
70
|
-
return true
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return false
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// domainDir accepts both layouts:
|
|
77
|
-
//
|
|
78
|
-
// internal/domain/user
|
|
79
|
-
// internal/user/domain
|
|
80
|
-
func domainDir(parts []string) (string, bool) {
|
|
81
|
-
if len(parts) == 3 && parts[0] == "internal" && parts[1] == "domain" {
|
|
82
|
-
return parts[2], true
|
|
83
|
-
}
|
|
84
|
-
if len(parts) == 3 && parts[0] == "internal" && parts[2] == "domain" {
|
|
85
|
-
return parts[1], true
|
|
86
|
-
}
|
|
87
|
-
return "", false
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// useCaseDir accepts the horizontal convention, feature slices with use cases
|
|
91
|
-
// directly below application, and feature slices that keep a usecases level.
|
|
92
|
-
func useCaseDir(parts []string) (aggregate, name string, ok bool) {
|
|
93
|
-
if len(parts) == 5 && parts[0] == "internal" && parts[1] == "application" && parts[3] == "usecases" {
|
|
94
|
-
return parts[2], parts[4], true
|
|
95
|
-
}
|
|
96
|
-
if len(parts) == 4 && parts[0] == "internal" && parts[2] == "application" {
|
|
97
|
-
return parts[1], parts[3], true
|
|
98
|
-
}
|
|
99
|
-
if len(parts) == 5 && parts[0] == "internal" && parts[2] == "application" && parts[3] == "usecases" {
|
|
100
|
-
return parts[1], parts[4], true
|
|
101
|
-
}
|
|
102
|
-
return "", "", false
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
func integrationEventDir(parts []string) (string, bool) {
|
|
106
|
-
if len(parts) == 4 && parts[0] == "internal" && parts[2] == "integration" && parts[3] == "event" {
|
|
107
|
-
return parts[1], true
|
|
108
|
-
}
|
|
109
|
-
// Legacy event DTOs lived beside repositories.
|
|
110
|
-
if len(parts) == 5 && parts[0] == "internal" && parts[1] == "infrastructure" && parts[2] == "repository" && parts[4] == "dto" {
|
|
111
|
-
return parts[3], true
|
|
112
|
-
}
|
|
113
|
-
return "", false
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
func isTransportPackage(parts []string, protocol string) bool {
|
|
117
|
-
for _, part := range parts {
|
|
118
|
-
if part == "gen" {
|
|
119
|
-
return false
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
for i, part := range parts {
|
|
123
|
-
if part != protocol || i == 0 {
|
|
124
|
-
continue
|
|
125
|
-
}
|
|
126
|
-
// Horizontal: infrastructure/transport/http[/handler].
|
|
127
|
-
// Feature: user/infrastructure/http[/handler].
|
|
128
|
-
// Shared: internal/transport/http.
|
|
129
|
-
if parts[i-1] == "transport" || parts[i-1] == "infrastructure" {
|
|
130
|
-
return true
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return false
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
func goPackageDirs(root, rel string) []string {
|
|
137
|
-
base := filepath.Join(root, filepath.FromSlash(rel))
|
|
138
|
-
seen := map[string]bool{}
|
|
139
|
-
|
|
140
|
-
_ = filepath.WalkDir(base, func(filename string, entry fs.DirEntry, err error) error {
|
|
141
|
-
if err != nil {
|
|
142
|
-
return nil
|
|
143
|
-
}
|
|
144
|
-
if entry.IsDir() {
|
|
145
|
-
if filename != base && strings.HasPrefix(entry.Name(), ".") {
|
|
146
|
-
return filepath.SkipDir
|
|
147
|
-
}
|
|
148
|
-
return nil
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
name := entry.Name()
|
|
152
|
-
if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") {
|
|
153
|
-
return nil
|
|
154
|
-
}
|
|
155
|
-
dir, err := filepath.Rel(root, filepath.Dir(filename))
|
|
156
|
-
if err == nil {
|
|
157
|
-
seen[filepath.ToSlash(dir)] = true
|
|
158
|
-
}
|
|
159
|
-
return nil
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
out := make([]string, 0, len(seen))
|
|
163
|
-
for dir := range seen {
|
|
164
|
-
out = append(out, dir)
|
|
165
|
-
}
|
|
166
|
-
sort.Strings(out)
|
|
167
|
-
return out
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
func internalParts(importPath string) ([]string, bool) {
|
|
171
|
-
_, after, found := strings.Cut(importPath, "/internal/")
|
|
172
|
-
if !found || after == "" {
|
|
173
|
-
return nil, false
|
|
174
|
-
}
|
|
175
|
-
return append([]string{"internal"}, strings.Split(after, "/")...), true
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
func domainImport(importPath string) (string, bool) {
|
|
179
|
-
parts, ok := internalParts(importPath)
|
|
180
|
-
if !ok {
|
|
181
|
-
return "", false
|
|
182
|
-
}
|
|
183
|
-
return domainDir(parts)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
func eventImport(importPath string) (string, bool) {
|
|
187
|
-
parts, ok := internalParts(importPath)
|
|
188
|
-
if !ok {
|
|
189
|
-
return "", false
|
|
190
|
-
}
|
|
191
|
-
if len(parts) == 4 && parts[0] == "internal" && parts[1] == "domain" && parts[3] == "event" {
|
|
192
|
-
return parts[2], true
|
|
193
|
-
}
|
|
194
|
-
if len(parts) == 4 && parts[0] == "internal" && parts[2] == "domain" && parts[3] == "event" {
|
|
195
|
-
return parts[1], true
|
|
196
|
-
}
|
|
197
|
-
return integrationEventDir(parts)
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
func useCaseImport(importPath string) (aggregate, name string, ok bool) {
|
|
201
|
-
parts, ok := internalParts(importPath)
|
|
202
|
-
if !ok {
|
|
203
|
-
return "", "", false
|
|
204
|
-
}
|
|
205
|
-
return useCaseDir(parts)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
func applicationSupportImport(importPath string) bool {
|
|
209
|
-
parts, ok := internalParts(importPath)
|
|
210
|
-
if !ok {
|
|
211
|
-
return false
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
return (len(parts) >= 3 && parts[1] == "application") ||
|
|
215
|
-
(len(parts) >= 4 && parts[2] == "application")
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
func sortedKeys[V any](values map[string]V) []string {
|
|
219
|
-
keys := make([]string, 0, len(values))
|
|
220
|
-
for key := range values {
|
|
221
|
-
keys = append(keys, key)
|
|
222
|
-
}
|
|
223
|
-
sort.Strings(keys)
|
|
224
|
-
return keys
|
|
225
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"encoding/json"
|
|
5
|
-
"os"
|
|
6
|
-
"path/filepath"
|
|
7
|
-
"strings"
|
|
8
|
-
"testing"
|
|
9
|
-
|
|
10
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
11
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
func TestLayoutClassifiersAcceptHorizontalAndFeatureSlices(t *testing.T) {
|
|
15
|
-
tests := []struct {
|
|
16
|
-
path string
|
|
17
|
-
aggregate string
|
|
18
|
-
useCase string
|
|
19
|
-
}{
|
|
20
|
-
{"github.com/example/auth/internal/domain/user", "user", ""},
|
|
21
|
-
{"github.com/example/auth/internal/user/domain", "user", ""},
|
|
22
|
-
{"github.com/example/auth/internal/application/user/usecases/register", "user", "register"},
|
|
23
|
-
{"github.com/example/auth/internal/user/application/register", "user", "register"},
|
|
24
|
-
{"github.com/example/auth/internal/user/application/usecases/register", "user", "register"},
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
for _, tt := range tests {
|
|
28
|
-
if tt.useCase == "" {
|
|
29
|
-
if got, ok := domainImport(tt.path); !ok || got != tt.aggregate {
|
|
30
|
-
t.Errorf("domainImport(%q) = %q, %v", tt.path, got, ok)
|
|
31
|
-
}
|
|
32
|
-
continue
|
|
33
|
-
}
|
|
34
|
-
aggregate, name, ok := useCaseImport(tt.path)
|
|
35
|
-
if !ok || aggregate != tt.aggregate || name != tt.useCase {
|
|
36
|
-
t.Errorf("useCaseImport(%q) = %q, %q, %v", tt.path, aggregate, name, ok)
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
func TestDiscoverLayoutFindsFeaturePackages(t *testing.T) {
|
|
42
|
-
root := t.TempDir()
|
|
43
|
-
write := func(name, contents string) {
|
|
44
|
-
t.Helper()
|
|
45
|
-
filename := filepath.Join(root, filepath.FromSlash(name))
|
|
46
|
-
if err := os.MkdirAll(filepath.Dir(filename), 0o755); err != nil {
|
|
47
|
-
t.Fatal(err)
|
|
48
|
-
}
|
|
49
|
-
if err := os.WriteFile(filename, []byte(contents), 0o644); err != nil {
|
|
50
|
-
t.Fatal(err)
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
write("internal/user/domain/user.go", "package user\ntype User struct{}\n")
|
|
55
|
-
write("internal/user/application/register/usecase.go", "package register\ntype UseCase struct{}\n")
|
|
56
|
-
write("internal/user/application/credentials/port.go", "package credentials\ntype PasswordHasher interface{}\n")
|
|
57
|
-
write("internal/user/integration/event/event.go", "package event\nconst Topic = \"users\"\n")
|
|
58
|
-
write("internal/user/infrastructure/http/handler.go", "package http\ntype Handler struct{}\n")
|
|
59
|
-
write("internal/session/infrastructure/messaging/policy/revoke.go", "package policy\ntype Revoke struct{}\n")
|
|
60
|
-
write("internal/transport/http/gen/server.gen.go", "package gen\ntype ServerInterface interface{}\n")
|
|
61
|
-
|
|
62
|
-
layout := discoverLayout(root)
|
|
63
|
-
if got := layout.domains["user"]; got != "internal/user/domain" {
|
|
64
|
-
t.Errorf("user domain = %q", got)
|
|
65
|
-
}
|
|
66
|
-
if got := layout.useCases["user/register"]; got != "internal/user/application/register" {
|
|
67
|
-
t.Errorf("register use case = %q", got)
|
|
68
|
-
}
|
|
69
|
-
if _, found := layout.useCases["user/credentials"]; found {
|
|
70
|
-
t.Error("an application support package was classified as a use case")
|
|
71
|
-
}
|
|
72
|
-
if got := layout.integrationEvents["user"]; got != "internal/user/integration/event" {
|
|
73
|
-
t.Errorf("integration events = %q", got)
|
|
74
|
-
}
|
|
75
|
-
if len(layout.http) != 1 || layout.http[0] != "internal/user/infrastructure/http" {
|
|
76
|
-
t.Errorf("http packages = %v", layout.http)
|
|
77
|
-
}
|
|
78
|
-
if len(layout.policies) != 1 || layout.policies[0] != "internal/session/infrastructure/messaging/policy" {
|
|
79
|
-
t.Errorf("policy packages = %v", layout.policies)
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
func TestExtractsFeatureSlicedAuthExample(t *testing.T) {
|
|
84
|
-
root := filepath.Join("..", "..", "examples", "auth")
|
|
85
|
-
resp, err := extract(plugin.Input{Root: root, Commit: "test", GeneratedAt: "2026-01-01T00:00:00Z"}, Options{
|
|
86
|
-
Context: "auth", Service: "auth", Store: "pg",
|
|
87
|
-
})
|
|
88
|
-
if err != nil {
|
|
89
|
-
t.Fatal(err)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
var got catalog.Catalog
|
|
93
|
-
if err := json.Unmarshal([]byte(resp.Files[0].Contents), &got); err != nil {
|
|
94
|
-
t.Fatal(err)
|
|
95
|
-
}
|
|
96
|
-
service := got.Contexts[0].Services[0]
|
|
97
|
-
if len(service.Aggregates) != 3 {
|
|
98
|
-
t.Fatalf("aggregates = %d, want user, session and lockout", len(service.Aggregates))
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
wantOperations := map[string]int{"user": 4, "session": 4, "lockout": 3}
|
|
102
|
-
for _, aggregate := range service.Aggregates {
|
|
103
|
-
if len(aggregate.Operations) != wantOperations[aggregate.Slug] {
|
|
104
|
-
t.Errorf("%s operations = %d, want %d", aggregate.Slug, len(aggregate.Operations), wantOperations[aggregate.Slug])
|
|
105
|
-
}
|
|
106
|
-
for _, event := range aggregate.Events {
|
|
107
|
-
if event.Wire == nil || event.Wire.Channel == "" {
|
|
108
|
-
t.Errorf("%s.%s has no integration-event channel", aggregate.Slug, event.Name)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
foundPolicy := false
|
|
114
|
-
for _, flow := range got.Flows {
|
|
115
|
-
if flow.ID == "flow.auth-revoke-sessions-on-password-change" {
|
|
116
|
-
foundPolicy = true
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
if !foundPolicy {
|
|
120
|
-
t.Error("the feature-local password-change policy was not extracted")
|
|
121
|
-
}
|
|
122
|
-
if !strings.Contains(resp.Files[0].Contents, `"ref": "auth.auth.lockout.AccountLocked"`) {
|
|
123
|
-
t.Error("AccountLocked collected into an event slice was not followed to the repository")
|
|
124
|
-
}
|
|
125
|
-
}
|
|
@@ -1,324 +0,0 @@
|
|
|
1
|
-
package main
|
|
2
|
-
|
|
3
|
-
import (
|
|
4
|
-
"go/ast"
|
|
5
|
-
"go/token"
|
|
6
|
-
"sort"
|
|
7
|
-
"strconv"
|
|
8
|
-
"strings"
|
|
9
|
-
|
|
10
|
-
"github.com/shortlink-org/portolan/catalog"
|
|
11
|
-
"github.com/shortlink-org/portolan/plugin"
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
// The root's lifecycle, read off a go-sdk/fsm rule set and the one method
|
|
15
|
-
// that runs events through it.
|
|
16
|
-
//
|
|
17
|
-
// var Rules = fsm.TransitionRuleSet{
|
|
18
|
-
// StateLive: {EventRevoke: StateRevoked},
|
|
19
|
-
// StateRevoked: {},
|
|
20
|
-
// }
|
|
21
|
-
// func (s *Session) trigger(ev fsm.Event, now time.Time) bool { … machine.TriggerEvent(ctx, ev) … }
|
|
22
|
-
// func (s *Session) Revoke(reason event.Reason, now time.Time) (event.SessionEnded, bool) { s.trigger(EventRevoke, now) … }
|
|
23
|
-
//
|
|
24
|
-
// The rule set gives the states, in the order the literal lists them, and the
|
|
25
|
-
// edges, each named by the fsm event that takes it. The method whose body
|
|
26
|
-
// calls TriggerEvent is the mover; every exported method that hands the mover
|
|
27
|
-
// a constant makes the edges that constant names. The event type a method
|
|
28
|
-
// returns is what its last move publishes: a Go method puts its guards and
|
|
29
|
-
// its housekeeping first and the change it announces at the end, so a method
|
|
30
|
-
// that lapses a lock and then locks again hands AccountLocked back for the
|
|
31
|
-
// lock, not for the lapse. A rule no method takes, a constant no rule knows,
|
|
32
|
-
// and a mover handed something that is not a constant are each reported,
|
|
33
|
-
// because the table is a claim and the claim should be kept.
|
|
34
|
-
func readLifecycle(p *pkg, root string, events []catalog.Event, aggID string, b *plugin.Builder) *catalog.Lifecycle {
|
|
35
|
-
consts := stringConsts(p)
|
|
36
|
-
states, rules := ruleSet(p, consts)
|
|
37
|
-
if len(states) == 0 {
|
|
38
|
-
return nil
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
mover := ""
|
|
42
|
-
methods := p.methods(root)
|
|
43
|
-
for name, fn := range methods {
|
|
44
|
-
if calls(fn, "TriggerEvent") {
|
|
45
|
-
mover = name
|
|
46
|
-
|
|
47
|
-
break
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if mover == "" {
|
|
51
|
-
b.Warn(aggID, p.dir+" declares a fsm.TransitionRuleSet but no method of "+root+" calls TriggerEvent, so nothing is read as moving along it")
|
|
52
|
-
|
|
53
|
-
return &catalog.Lifecycle{States: states, Transitions: []catalog.Transition{}}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
byName := map[string]string{}
|
|
57
|
-
for _, ev := range events {
|
|
58
|
-
byName[ev.Name] = ev.ID
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
var transitions []catalog.Transition
|
|
62
|
-
taken := map[string]bool{}
|
|
63
|
-
for name, fn := range methods {
|
|
64
|
-
if name == mover || !exported(name) || fn.Body == nil {
|
|
65
|
-
continue
|
|
66
|
-
}
|
|
67
|
-
emits := emittedEvent(fn, byName)
|
|
68
|
-
var made []catalog.Transition
|
|
69
|
-
ast.Inspect(fn.Body, func(node ast.Node) bool {
|
|
70
|
-
call, ok := node.(*ast.CallExpr)
|
|
71
|
-
if !ok {
|
|
72
|
-
return true
|
|
73
|
-
}
|
|
74
|
-
sel, ok := call.Fun.(*ast.SelectorExpr)
|
|
75
|
-
if !ok || (sel.Sel.Name != mover && sel.Sel.Name != "TriggerEvent") {
|
|
76
|
-
return true
|
|
77
|
-
}
|
|
78
|
-
event, literal := eventArg(call, consts)
|
|
79
|
-
if !literal {
|
|
80
|
-
b.Warn(aggID, p.at(call.Pos())+": "+name+" hands "+sel.Sel.Name+" something that is not a constant; the move is not in the lifecycle")
|
|
81
|
-
|
|
82
|
-
return true
|
|
83
|
-
}
|
|
84
|
-
known := false
|
|
85
|
-
for _, from := range states {
|
|
86
|
-
to, ok := rules[from][event]
|
|
87
|
-
if !ok {
|
|
88
|
-
continue
|
|
89
|
-
}
|
|
90
|
-
known = true
|
|
91
|
-
taken[from+"→"+to] = true
|
|
92
|
-
made = append(made, catalog.Transition{From: from, To: to, On: name, Source: p.at(call.Pos())})
|
|
93
|
-
}
|
|
94
|
-
if !known {
|
|
95
|
-
b.Warn(aggID, p.at(call.Pos())+": "+name+" triggers "+strconv.Quote(event)+", which no rule in the table knows")
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return true
|
|
99
|
-
})
|
|
100
|
-
// The event goes with the last move the method makes: see above.
|
|
101
|
-
if len(made) > 0 && emits != "" {
|
|
102
|
-
last := made[len(made)-1].Source
|
|
103
|
-
for i := range made {
|
|
104
|
-
if made[i].Source == last {
|
|
105
|
-
made[i].Emits = emits
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
transitions = append(transitions, made...)
|
|
110
|
-
}
|
|
111
|
-
for _, from := range states {
|
|
112
|
-
for event, to := range rules[from] {
|
|
113
|
-
if !taken[from+"→"+to] {
|
|
114
|
-
b.Warn(aggID, "the table allows "+from+" → "+to+" on "+strconv.Quote(event)+", and no method of "+root+" makes that move")
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
order := map[string]int{}
|
|
120
|
-
for i, s := range states {
|
|
121
|
-
order[s] = i
|
|
122
|
-
}
|
|
123
|
-
sort.SliceStable(transitions, func(i, j int) bool {
|
|
124
|
-
a, c := transitions[i], transitions[j]
|
|
125
|
-
if order[a.From] != order[c.From] {
|
|
126
|
-
return order[a.From] < order[c.From]
|
|
127
|
-
}
|
|
128
|
-
if order[a.To] != order[c.To] {
|
|
129
|
-
return order[a.To] < order[c.To]
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
return a.On < c.On
|
|
133
|
-
})
|
|
134
|
-
if transitions == nil {
|
|
135
|
-
transitions = []catalog.Transition{}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return &catalog.Lifecycle{States: states, Transitions: transitions}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// ruleSet finds a package-level `fsm.TransitionRuleSet{…}` literal and reads
|
|
142
|
-
// it as states, in literal order, and from → event → to. A state that only
|
|
143
|
-
// ever appears as a target is appended, so the table can leave a terminal
|
|
144
|
-
// state out and the page still draws it.
|
|
145
|
-
func ruleSet(p *pkg, consts map[string]string) ([]string, map[string]map[string]string) {
|
|
146
|
-
rules := map[string]map[string]string{}
|
|
147
|
-
var states []string
|
|
148
|
-
seen := map[string]bool{}
|
|
149
|
-
add := func(state string) {
|
|
150
|
-
if !seen[state] {
|
|
151
|
-
seen[state] = true
|
|
152
|
-
states = append(states, state)
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
for _, file := range p.files {
|
|
157
|
-
for _, decl := range file.Decls {
|
|
158
|
-
gen, ok := decl.(*ast.GenDecl)
|
|
159
|
-
if !ok || gen.Tok != token.VAR {
|
|
160
|
-
continue
|
|
161
|
-
}
|
|
162
|
-
for _, spec := range gen.Specs {
|
|
163
|
-
value, ok := spec.(*ast.ValueSpec)
|
|
164
|
-
if !ok {
|
|
165
|
-
continue
|
|
166
|
-
}
|
|
167
|
-
for _, v := range value.Values {
|
|
168
|
-
lit, ok := v.(*ast.CompositeLit)
|
|
169
|
-
if !ok || !isRuleSetType(lit.Type) {
|
|
170
|
-
continue
|
|
171
|
-
}
|
|
172
|
-
for _, elt := range lit.Elts {
|
|
173
|
-
kv, ok := elt.(*ast.KeyValueExpr)
|
|
174
|
-
if !ok {
|
|
175
|
-
continue
|
|
176
|
-
}
|
|
177
|
-
from, ok := constString(kv.Key, consts)
|
|
178
|
-
if !ok {
|
|
179
|
-
continue
|
|
180
|
-
}
|
|
181
|
-
add(from)
|
|
182
|
-
if rules[from] == nil {
|
|
183
|
-
rules[from] = map[string]string{}
|
|
184
|
-
}
|
|
185
|
-
inner, ok := kv.Value.(*ast.CompositeLit)
|
|
186
|
-
if !ok {
|
|
187
|
-
continue
|
|
188
|
-
}
|
|
189
|
-
for _, e := range inner.Elts {
|
|
190
|
-
pair, ok := e.(*ast.KeyValueExpr)
|
|
191
|
-
if !ok {
|
|
192
|
-
continue
|
|
193
|
-
}
|
|
194
|
-
event, ok1 := constString(pair.Key, consts)
|
|
195
|
-
to, ok2 := constString(pair.Value, consts)
|
|
196
|
-
if ok1 && ok2 {
|
|
197
|
-
rules[from][event] = to
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
for _, from := range append([]string(nil), states...) {
|
|
206
|
-
for _, to := range rules[from] {
|
|
207
|
-
add(to)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return states, rules
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
func isRuleSetType(expr ast.Expr) bool {
|
|
215
|
-
sel, ok := expr.(*ast.SelectorExpr)
|
|
216
|
-
if !ok {
|
|
217
|
-
return false
|
|
218
|
-
}
|
|
219
|
-
pkg, ok := sel.X.(*ast.Ident)
|
|
220
|
-
|
|
221
|
-
return ok && pkg.Name == "fsm" && sel.Sel.Name == "TransitionRuleSet"
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// eventArg finds the fsm event a call hands over: the constant among its
|
|
225
|
-
// arguments, whichever position the mover keeps it in.
|
|
226
|
-
func eventArg(call *ast.CallExpr, consts map[string]string) (string, bool) {
|
|
227
|
-
for _, arg := range call.Args {
|
|
228
|
-
if s, ok := constString(arg, consts); ok {
|
|
229
|
-
return s, true
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return "", false
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// constString reads an expression as a string it can be sure of: a literal,
|
|
237
|
-
// or a name the package declares as one.
|
|
238
|
-
func constString(expr ast.Expr, consts map[string]string) (string, bool) {
|
|
239
|
-
switch e := expr.(type) {
|
|
240
|
-
case *ast.BasicLit:
|
|
241
|
-
if e.Kind == token.STRING {
|
|
242
|
-
s, err := strconv.Unquote(e.Value)
|
|
243
|
-
|
|
244
|
-
return s, err == nil
|
|
245
|
-
}
|
|
246
|
-
case *ast.Ident:
|
|
247
|
-
s, ok := consts[e.Name]
|
|
248
|
-
|
|
249
|
-
return s, ok
|
|
250
|
-
case *ast.ParenExpr:
|
|
251
|
-
return constString(e.X, consts)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return "", false
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// stringConsts collects the package's string constants by name, typed or not:
|
|
258
|
-
// `StateLive fsm.State = "live"` is "live".
|
|
259
|
-
func stringConsts(p *pkg) map[string]string {
|
|
260
|
-
out := map[string]string{}
|
|
261
|
-
for _, file := range p.files {
|
|
262
|
-
for _, decl := range file.Decls {
|
|
263
|
-
gen, ok := decl.(*ast.GenDecl)
|
|
264
|
-
if !ok || gen.Tok != token.CONST {
|
|
265
|
-
continue
|
|
266
|
-
}
|
|
267
|
-
for _, spec := range gen.Specs {
|
|
268
|
-
value, ok := spec.(*ast.ValueSpec)
|
|
269
|
-
if !ok {
|
|
270
|
-
continue
|
|
271
|
-
}
|
|
272
|
-
for i, name := range value.Names {
|
|
273
|
-
if i >= len(value.Values) {
|
|
274
|
-
break
|
|
275
|
-
}
|
|
276
|
-
if s, ok := constString(value.Values[i], nil); ok {
|
|
277
|
-
out[name.Name] = s
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
return out
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// emittedEvent is the event type among a method's results, when one of the
|
|
288
|
-
// aggregate's events is there: `(event.SessionEnded, bool)` emits SessionEnded.
|
|
289
|
-
func emittedEvent(fn *ast.FuncDecl, byName map[string]string) string {
|
|
290
|
-
if fn.Type.Results == nil {
|
|
291
|
-
return ""
|
|
292
|
-
}
|
|
293
|
-
for _, field := range fn.Type.Results.List {
|
|
294
|
-
expr := field.Type
|
|
295
|
-
if star, ok := expr.(*ast.StarExpr); ok {
|
|
296
|
-
expr = star.X
|
|
297
|
-
}
|
|
298
|
-
name := ""
|
|
299
|
-
switch t := expr.(type) {
|
|
300
|
-
case *ast.SelectorExpr:
|
|
301
|
-
name = t.Sel.Name
|
|
302
|
-
case *ast.Ident:
|
|
303
|
-
name = t.Name
|
|
304
|
-
}
|
|
305
|
-
if id, ok := byName[name]; ok {
|
|
306
|
-
return id
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
return ""
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// at is a position as a reader would open it: the package-relative path and
|
|
314
|
-
// the line.
|
|
315
|
-
func (p *pkg) at(pos token.Pos) string {
|
|
316
|
-
for file, rel := range p.paths {
|
|
317
|
-
if file.Pos() <= pos && pos <= file.End() {
|
|
318
|
-
return rel + ":" + strconv.Itoa(p.fset.Position(pos).Line)
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
name, line := p.position(pos)
|
|
322
|
-
|
|
323
|
-
return strings.TrimPrefix(name, p.dir+"/") + ":" + strconv.Itoa(line)
|
|
324
|
-
}
|