@shortlink-org/portolan 0.2.4 → 0.4.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 +174 -6
- package/catalog/enum_test.go +46 -0
- package/catalog/evidence_test.go +35 -0
- package/catalog/model.go +1213 -0
- package/catalog/roundtrip_test.go +210 -0
- package/catalog/via_test.go +38 -0
- package/cli/init.test.mjs +6 -1
- package/cli/portolan.mjs +14 -1
- package/cli/portolan.test.mjs +49 -0
- package/go.mod +21 -0
- package/go.sum +34 -0
- package/internal/gocall/README.md +19 -0
- package/internal/gocall/analyze.go +189 -0
- package/internal/gocall/analyze_test.go +107 -0
- package/internal/gohttp/analyze.go +2562 -0
- package/internal/gohttp/destination.go +373 -0
- package/internal/gohttp/endpoints.go +1067 -0
- package/internal/gohttp/roots.go +320 -0
- package/internal/gohttp/typed.go +96 -0
- package/internal/goscan/constants.go +85 -0
- package/internal/goscan/goscan_test.go +227 -0
- package/internal/goscan/index.go +629 -0
- package/internal/goscan/index_test.go +66 -0
- package/internal/goscan/names.go +52 -0
- package/internal/goscan/parse_test.go +11 -0
- package/internal/goscan/source.go +37 -0
- package/internal/goscan/tree.go +284 -0
- package/internal/goscan/types.go +99 -0
- package/internal/wsdl/ids.go +127 -0
- package/internal/wsdl/ids_test.go +21 -0
- package/internal/wsdl/model.go +70 -0
- package/internal/wsdl/parse.go +949 -0
- package/internal/wsdl/parse_test.go +170 -0
- package/package.json +28 -15
- package/plugin/describe.go +156 -0
- package/plugin/describe_test.go +114 -0
- package/plugin/protocol.go +141 -0
- package/plugin/schematest/schematest.go +126 -0
- package/plugins/README.md +404 -50
- package/plugins/cmd/portolan-http-clients/main.go +19 -0
- package/plugins/extract-argocd/options.schema.json +44 -0
- package/plugins/extract-celery/extract.py +0 -2
- package/plugins/extract-celery/extract_test.py +1 -1
- package/plugins/extract-celery/main.py +1 -0
- package/plugins/extract-csharp-ddd/README.md +213 -0
- package/plugins/extract-csharp-ddd/options.schema.json +55 -0
- package/plugins/extract-django/README.md +52 -19
- package/plugins/extract-django/choices.py +47 -0
- package/plugins/extract-django/domain.py +30 -18
- package/plugins/extract-django/extract.py +21 -7
- package/plugins/extract-django/extract_test.py +68 -2
- package/plugins/extract-django/lifecycle.py +4 -28
- package/plugins/extract-django/main.py +1 -0
- package/plugins/extract-django/operations.py +1 -1
- package/plugins/extract-django/routing_test.py +109 -1
- package/plugins/extract-django/rules.py +154 -0
- package/plugins/extract-django/rules_test.py +158 -0
- package/plugins/extract-django/store.py +1 -1
- package/plugins/extract-django/transport.py +101 -55
- package/plugins/extract-django/verbs.py +241 -0
- package/plugins/extract-go/README.md +47 -0
- package/plugins/extract-go-sqs/options.schema.json +21 -0
- package/plugins/extract-http-clients/describe.go +20 -0
- package/plugins/extract-http-clients/describe_test.go +11 -0
- package/plugins/extract-http-clients/extract.go +740 -0
- package/plugins/extract-http-clients/extract_test.go +1561 -0
- package/plugins/extract-http-clients/main.go +41 -0
- package/plugins/extract-java/build/org/portolan/extract/Extract.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Main.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Builder.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Input.class +0 -0
- package/plugins/extract-java/build/org/portolan/extract/Protocol$Options.class +0 -0
- package/plugins/extract-k8s/options.schema.json +32 -0
- package/plugins/extract-laravel/Cargo.lock +962 -0
- package/plugins/extract-laravel/Cargo.toml +20 -0
- package/plugins/extract-laravel/README.md +200 -0
- package/plugins/extract-laravel/options.schema.json +68 -0
- package/plugins/extract-laravel/rustfmt.toml +1 -0
- package/plugins/extract-php-ddd/Cargo.lock +972 -0
- package/plugins/extract-php-ddd/Cargo.toml +22 -0
- package/plugins/extract-php-ddd/README.md +141 -0
- package/plugins/extract-php-ddd/options.schema.json +50 -0
- package/plugins/extract-php-ddd/rustfmt.toml +1 -0
- package/plugins/extract-python-kafka/extract.py +0 -2
- package/plugins/extract-python-kafka/extract_test.py +1 -1
- package/plugins/extract-python-kafka/main.py +1 -0
- package/plugins/extract-terraform/options.schema.json +26 -0
- package/plugins/extract-ts/extract.test.ts +2 -2
- package/plugins/extract-ts/extract.ts +4 -5
- package/plugins/extract-ts/graphql.test.ts +1 -1
- package/plugins/extract-ts/main.ts +1 -0
- package/plugins/openapi/ids.go +261 -0
- package/plugins/openapi/ids_test.go +98 -0
- package/plugins/phpscan/Cargo.lock +953 -0
- package/plugins/phpscan/Cargo.toml +19 -0
- package/plugins/phpscan/rustfmt.toml +1 -0
- package/plugins/portolan-go.wasm +0 -0
- package/plugins/pyplugin/catalog.py +24 -1
- package/plugins/pyplugin/protocol.py +1 -5
- package/plugins/verify-otel/options.schema.json +12 -0
- package/portolan.json +132 -4
- package/rules/builtin.json +218 -0
- package/schema/portolan.schema.json +905 -4
- package/scripts/README.md +21 -13
- package/scripts/catalog-sources.mjs +6 -0
- package/scripts/delivery-presets.mjs +57 -14
- package/scripts/diff.mjs +5 -1
- package/scripts/django-aggregates.test.mjs +58 -0
- package/scripts/gen-likec4.mjs +150 -17
- package/scripts/gen-likec4.test.mjs +96 -0
- package/scripts/gen.mjs +148 -118
- package/scripts/gitops-example.test.mjs +108 -0
- package/scripts/go-discovery.test.mjs +30 -0
- package/scripts/history.mjs +186 -3
- package/scripts/history.test.mjs +1 -1
- package/scripts/host-plugins/fetch-argocd.mjs +338 -0
- package/scripts/host-plugins/fetch-argocd.options.json +49 -0
- package/scripts/host-plugins/fetch-argocd.test.mjs +274 -0
- package/scripts/host-plugins/fetch-bsr.mjs +1 -0
- package/scripts/host-plugins/fetch-csr.mjs +1 -0
- package/scripts/host-plugins/fetch-git.mjs +78 -21
- package/scripts/host-plugins/fetch-git.test.mjs +62 -8
- package/scripts/host-plugins/fetch-k8s.mjs +263 -0
- package/scripts/host-plugins/fetch-k8s.options.json +50 -0
- package/scripts/host-plugins/fetch-k8s.test.mjs +259 -0
- package/scripts/host-plugins/k8s-topology.mjs +183 -0
- package/scripts/json-format.mjs +192 -0
- package/scripts/json-format.test.mjs +97 -0
- package/scripts/local-api.mjs +287 -12
- package/scripts/local-api.test.mjs +150 -5
- package/scripts/local-discovery.mjs +94 -9
- package/scripts/manifest.mjs +14 -3
- package/scripts/manifest.test.mjs +24 -0
- package/scripts/output-diff.mjs +94 -0
- package/scripts/output-diff.test.mjs +36 -0
- package/scripts/package-smoke.mjs +62 -4
- package/scripts/plugin-host.mjs +45 -3
- package/scripts/plugin-host.test.mjs +9 -0
- package/scripts/plugin-wasm-worker.mjs +4 -1
- package/scripts/plugins-fresh.mjs +97 -0
- package/scripts/plugins-fresh.test.mjs +64 -0
- package/scripts/provenance.mjs +72 -0
- package/scripts/provenance.test.mjs +149 -0
- package/scripts/run-builtin.mjs +62 -7
- package/scripts/schema.mjs +167 -19
- package/scripts/trace-trials.mjs +176 -0
- package/scripts/trace-trials.test.mjs +142 -0
- package/scripts/warning-policy.mjs +167 -0
- package/scripts/warning-policy.test.mjs +93 -0
- package/src/app/Breadcrumbs.test.ts +3 -0
- package/src/app/Breadcrumbs.tsx +3 -0
- package/src/app/CatalogApp.tsx +2 -0
- package/src/app/Sidebar.tsx +3 -3
- package/src/app/SidebarFooter.tsx +20 -4
- package/src/catalog-docs.test.ts +64 -0
- package/src/catalog-docs.ts +35 -0
- package/src/catalog-error.test.ts +15 -0
- package/src/catalog-index.ts +25 -0
- package/src/catalog-model.ts +293 -5
- package/src/catalog-validation.ts +113 -2
- package/src/catalog.test.ts +40 -1
- package/src/chat/Starter.tsx +5 -11
- package/src/chat/tools.test.ts +27 -0
- package/src/chat/tools.ts +5 -9
- package/src/components/CatalogStamp.tsx +10 -8
- package/src/components/ChannelRows.test.tsx +76 -1
- package/src/components/ChannelRows.tsx +223 -60
- package/src/components/DeploymentRows.tsx +131 -0
- package/src/components/DocsLinks.test.tsx +27 -0
- package/src/components/DocsLinks.tsx +56 -0
- package/src/components/FieldTree.tsx +5 -2
- package/src/components/HTTPDestinationEvidence.test.tsx +23 -0
- package/src/components/HTTPDestinationEvidence.tsx +31 -0
- package/src/components/Integrations.tsx +1 -1
- package/src/components/MachineDocs.tsx +6 -5
- package/src/components/MethodRows.tsx +9 -2
- package/src/components/PluginIcon.tsx +77 -0
- package/src/components/ProblemRow.tsx +173 -153
- package/src/components/RelationEvidence.test.tsx +14 -0
- package/src/components/RelationEvidence.tsx +53 -0
- package/src/components/RuleMarks.tsx +22 -0
- package/src/components/ShapeRows.tsx +24 -20
- package/src/data.ts +46 -7
- package/src/enrich.test.ts +459 -4
- package/src/enrich.ts +308 -7
- package/src/er/ErCanvas.tsx +218 -12
- package/src/er/GroupNode.tsx +57 -0
- package/src/er/StoreHeader.tsx +1 -0
- package/src/er/layout.test.ts +85 -2
- package/src/er/layout.ts +140 -5
- package/src/er/spec.test.ts +17 -0
- package/src/er/spec.ts +23 -10
- package/src/flow/Recordings.test.tsx +52 -0
- package/src/flow/Recordings.tsx +236 -0
- package/src/flow/StepDetail.tsx +59 -0
- package/src/flow/TraceTrial.tsx +419 -0
- package/src/flow/evidence.test.ts +16 -0
- package/src/flow/evidence.ts +34 -0
- package/src/flow/examples.test.ts +33 -0
- package/src/flow/examples.ts +37 -0
- package/src/flow/outline.test.ts +28 -0
- package/src/flow/outline.ts +7 -2
- package/src/flow/trace-trial-resume.test.ts +42 -0
- package/src/flow/trace-trial-resume.ts +74 -0
- package/src/graph/elk.ts +78 -0
- package/src/index.css +68 -0
- package/src/landing/DraggableReveal.tsx +3 -2
- package/src/landing/EvidencePipeline.tsx +105 -0
- package/src/landing/LandingPage.tsx +17 -70
- package/src/landing/ProductTour.tsx +6 -6
- package/src/lib/all-problems.ts +27 -17
- package/src/lib/catalog-diff.ts +1 -1
- package/src/lib/centrality.test.ts +251 -0
- package/src/lib/centrality.ts +232 -0
- package/src/lib/confluence.test.ts +37 -0
- package/src/lib/confluence.ts +41 -0
- package/src/lib/context-color.ts +1 -1
- package/src/lib/deployment-drift.ts +22 -0
- package/src/lib/derive.ts +43 -69
- package/src/lib/django-aggregates.d.mts +9 -0
- package/src/lib/django-aggregates.mjs +36 -0
- package/src/lib/django-aggregates.test.ts +29 -0
- package/src/lib/django-aggregates.ts +5 -0
- package/src/lib/environments.test.ts +83 -0
- package/src/lib/environments.ts +57 -0
- package/src/lib/integration-url.test.ts +30 -0
- package/src/lib/integration-url.ts +63 -0
- package/src/lib/kafka-ui.ts +3 -45
- package/src/lib/local-api.ts +116 -4
- package/src/lib/notion.ts +13 -0
- package/src/lib/plugin-index.json +2765 -0
- package/src/lib/plugins.test.ts +68 -0
- package/src/lib/plugins.ts +264 -0
- package/src/lib/problem-flows.test.ts +61 -0
- package/src/lib/problem-flows.ts +78 -0
- package/src/lib/problem-rules-cel.d.mts +43 -0
- package/src/lib/problem-rules-cel.mjs +407 -0
- package/src/lib/problem-rules.test.ts +287 -0
- package/src/lib/problem-rules.ts +271 -0
- package/src/lib/problem-subjects.ts +737 -0
- package/src/lib/rule-entries.ts +39 -0
- package/src/lib/{data-problems.test.ts → rules-data.test.ts} +18 -16
- package/src/lib/rules-deploy.test.ts +133 -0
- package/src/lib/{problems.test.ts → rules-edges.test.ts} +8 -3
- package/src/lib/{proto-problems.test.ts → rules-proto.test.ts} +9 -25
- package/src/lib/{wire-problems.test.ts → rules-wire.test.ts} +13 -11
- package/src/lib/rules.test.ts +51 -0
- package/src/lib/rules.ts +86 -0
- package/src/lib/setup-info.test.ts +17 -0
- package/src/lib/setup-info.ts +58 -0
- package/src/lib/shape.test.ts +32 -0
- package/src/lib/shape.ts +30 -6
- package/src/lib/tech.ts +16 -0
- package/src/lib/trace-project.test.ts +34 -0
- package/src/lib/trace-project.ts +50 -0
- package/src/lib/use-problems.ts +23 -0
- package/src/lib/warnings.test.ts +63 -0
- package/src/lib/warnings.ts +260 -0
- package/src/likec4/ids.test.ts +6 -2
- package/src/likec4/ids.ts +43 -0
- package/src/main.tsx +23 -0
- package/src/map/ContextMapGraph.tsx +76 -32
- package/src/merge-deployments.test.ts +127 -0
- package/src/merge.test.ts +82 -0
- package/src/merge.ts +189 -18
- package/src/pages/AggregatePage.tsx +65 -14
- package/src/pages/ContextMap.tsx +45 -3
- package/src/pages/ContextPage.tsx +8 -5
- package/src/pages/EventPage.tsx +15 -5
- package/src/pages/FlowDetail.tsx +23 -2
- package/src/pages/GraphPage.tsx +40 -3
- package/src/pages/Overview.tsx +152 -12
- package/src/pages/PluginIndex.tsx +190 -0
- package/src/pages/Problems.tsx +396 -128
- package/src/pages/ServicePage.tsx +62 -5
- package/src/pages/Settings.tsx +217 -43
- package/src/pages/settings/AboutSettings.tsx +8 -1
- package/src/pages/settings/DjangoAggregateChoices.tsx +79 -0
- package/src/pages/settings/IntegrationsSettings.tsx +63 -17
- package/src/pages/settings/RecordingSettings.tsx +138 -0
- package/src/pages/settings/RulesSettings.tsx +825 -0
- package/src/routes.test.ts +9 -0
- package/src/routes.ts +22 -1
- package/src/selection/DetailPanel.tsx +15 -0
- package/src/virtual-provenance.d.ts +11 -0
- package/vite.config.ts +5 -0
- package/scripts/vendor-lock.mjs +0 -58
- package/scripts/vendor-lock.test.mjs +0 -69
- package/src/lib/data-problems.ts +0 -314
- package/src/lib/proto-problems.ts +0 -237
- package/src/lib/wire-problems.ts +0 -342
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
package goscan
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"go/ast"
|
|
5
|
+
"go/token"
|
|
6
|
+
"reflect"
|
|
7
|
+
"sort"
|
|
8
|
+
"strconv"
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
// StructType is a struct as a reader needs it: what type each field has, so
|
|
12
|
+
// that `b.conn` is known to be a connection, and what a field defaults to
|
|
13
|
+
// when its tag says, so that a subject or a topic read off config resolves.
|
|
14
|
+
type StructType struct {
|
|
15
|
+
Key string
|
|
16
|
+
Fields map[string]string
|
|
17
|
+
Defaults map[string]string
|
|
18
|
+
// Env is the environment variable a field is configured by, when its
|
|
19
|
+
// `envconfig` or `env` tag says; documentation for the default's origin.
|
|
20
|
+
Env map[string]string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Function is one declared function or method: its parameters by name and
|
|
24
|
+
// type, what it returns, and what its body assigns to which name.
|
|
25
|
+
type Function struct {
|
|
26
|
+
Key string
|
|
27
|
+
Name string
|
|
28
|
+
Receiver string
|
|
29
|
+
File *File
|
|
30
|
+
Decl *ast.FuncDecl
|
|
31
|
+
Params []string
|
|
32
|
+
Types map[string]string
|
|
33
|
+
Results []string
|
|
34
|
+
assigned map[string]Assignment
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Assignment is the last thing a local name was given: the expression, and
|
|
38
|
+
// which of its values when the expression yields several.
|
|
39
|
+
type Assignment struct {
|
|
40
|
+
Expr ast.Expr
|
|
41
|
+
Index int
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Resolved is one string an expression was found to be, where it was
|
|
45
|
+
// written, and - when it came through a wrapper that also names the message -
|
|
46
|
+
// what the message is called.
|
|
47
|
+
type Resolved struct {
|
|
48
|
+
Value string
|
|
49
|
+
At Source
|
|
50
|
+
Name string
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// CallArg is one expression a caller passes for a parameter, with the
|
|
54
|
+
// function it is written in, so that it can be read in that function's scope.
|
|
55
|
+
type CallArg struct {
|
|
56
|
+
Expr ast.Expr
|
|
57
|
+
Fn *Function
|
|
58
|
+
Call *ast.CallExpr
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Index is the tree, and the declarations in it a call is read against:
|
|
62
|
+
// the structs with their field types and defaults, the interfaces by their
|
|
63
|
+
// method names, and every function with what it takes and returns. It is
|
|
64
|
+
// what lets an extractor follow a value through a local, a field, a
|
|
65
|
+
// constructor and a parameter up to the caller that passed it - the same
|
|
66
|
+
// walk for a NATS subject, a Watermill topic and a River queue.
|
|
67
|
+
type Index struct {
|
|
68
|
+
*Tree
|
|
69
|
+
Structs map[string]*StructType
|
|
70
|
+
Interfaces map[string][]string
|
|
71
|
+
// Named is every type the tree declares, by key, whatever its shape.
|
|
72
|
+
Named map[string]bool
|
|
73
|
+
Functions map[string]*Function
|
|
74
|
+
Methods map[string][]*Function
|
|
75
|
+
ByName map[string][]*Function
|
|
76
|
+
// KnownResults is what constructors outside the tree hand back, by
|
|
77
|
+
// "<import path>.<Func>" or "<type key>.<Method>": nats.Connect gives a
|
|
78
|
+
// *nats.Conn, and nothing in the tree says so. Set by the extractor.
|
|
79
|
+
KnownResults map[string][]string
|
|
80
|
+
// Companion, when set, is which other parameter of fn names the message
|
|
81
|
+
// carried beside the value at index - the shape of a bus port,
|
|
82
|
+
// Subscribe(subject, name, handler). -1 when there is none.
|
|
83
|
+
Companion func(fn *Function, index int) int
|
|
84
|
+
// Hops is how far up the callers a parameter is followed. One hop is the
|
|
85
|
+
// port: the adapter takes the value as a parameter and the assembly
|
|
86
|
+
// passes a constant. Two is an assembly that itself was handed it.
|
|
87
|
+
// Further than that is not a declaration any more.
|
|
88
|
+
Hops int
|
|
89
|
+
// typing is the locals whose type is being worked out right now, so a
|
|
90
|
+
// name shadowed by an expression of itself - `js := js.Sub()` in an
|
|
91
|
+
// inner block - ends rather than recurses.
|
|
92
|
+
typing map[string]bool
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// NewIndex reads every type and function declaration of the tree.
|
|
96
|
+
func NewIndex(tree *Tree) *Index {
|
|
97
|
+
s := &Index{
|
|
98
|
+
Tree: tree,
|
|
99
|
+
Structs: map[string]*StructType{},
|
|
100
|
+
Interfaces: map[string][]string{},
|
|
101
|
+
Named: map[string]bool{},
|
|
102
|
+
Functions: map[string]*Function{},
|
|
103
|
+
Methods: map[string][]*Function{},
|
|
104
|
+
ByName: map[string][]*Function{},
|
|
105
|
+
Hops: 2,
|
|
106
|
+
typing: map[string]bool{},
|
|
107
|
+
}
|
|
108
|
+
for _, file := range tree.Files {
|
|
109
|
+
for _, decl := range file.Node.Decls {
|
|
110
|
+
switch d := decl.(type) {
|
|
111
|
+
case *ast.GenDecl:
|
|
112
|
+
if d.Tok != token.TYPE {
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
for _, raw := range d.Specs {
|
|
116
|
+
s.indexType(file, raw.(*ast.TypeSpec))
|
|
117
|
+
}
|
|
118
|
+
case *ast.FuncDecl:
|
|
119
|
+
s.indexFunction(file, d)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return s
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
func (s *Index) indexType(file *File, spec *ast.TypeSpec) {
|
|
127
|
+
key := file.Pkg + "." + spec.Name.Name
|
|
128
|
+
s.Named[key] = true
|
|
129
|
+
switch body := spec.Type.(type) {
|
|
130
|
+
case *ast.StructType:
|
|
131
|
+
st := &StructType{Key: key, Fields: map[string]string{}, Defaults: map[string]string{}, Env: map[string]string{}}
|
|
132
|
+
for _, field := range body.Fields.List {
|
|
133
|
+
typeKey := s.TypeKey(field.Type, file)
|
|
134
|
+
def, env := "", ""
|
|
135
|
+
if field.Tag != nil {
|
|
136
|
+
text, _ := strconv.Unquote(field.Tag.Value)
|
|
137
|
+
tag := reflect.StructTag(text)
|
|
138
|
+
def = tag.Get("default")
|
|
139
|
+
env = FirstNonEmpty(tag.Get("envconfig"), tag.Get("env"))
|
|
140
|
+
}
|
|
141
|
+
for _, name := range field.Names {
|
|
142
|
+
st.Fields[name.Name] = typeKey
|
|
143
|
+
if def != "" {
|
|
144
|
+
st.Defaults[name.Name] = def
|
|
145
|
+
}
|
|
146
|
+
if env != "" {
|
|
147
|
+
st.Env[name.Name] = env
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
s.Structs[key] = st
|
|
152
|
+
case *ast.InterfaceType:
|
|
153
|
+
// Embedded interfaces are not followed: the port a service writes for
|
|
154
|
+
// its bus names its methods itself.
|
|
155
|
+
var names []string
|
|
156
|
+
for _, method := range body.Methods.List {
|
|
157
|
+
for _, name := range method.Names {
|
|
158
|
+
names = append(names, name.Name)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
sort.Strings(names)
|
|
162
|
+
s.Interfaces[key] = names
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
func (s *Index) indexFunction(file *File, decl *ast.FuncDecl) {
|
|
167
|
+
key := file.Pkg + "." + decl.Name.Name
|
|
168
|
+
receiver := ""
|
|
169
|
+
if decl.Recv != nil && len(decl.Recv.List) > 0 {
|
|
170
|
+
receiver = s.TypeKey(decl.Recv.List[0].Type, file)
|
|
171
|
+
key = receiver + "." + decl.Name.Name
|
|
172
|
+
}
|
|
173
|
+
fn := &Function{Key: key, Name: decl.Name.Name, Receiver: receiver, File: file, Decl: decl, Types: map[string]string{}}
|
|
174
|
+
if decl.Recv != nil {
|
|
175
|
+
for _, field := range decl.Recv.List {
|
|
176
|
+
for _, name := range field.Names {
|
|
177
|
+
fn.Types[name.Name] = receiver
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
if decl.Type.Params != nil {
|
|
182
|
+
for _, field := range decl.Type.Params.List {
|
|
183
|
+
typeKey := s.TypeKey(field.Type, file)
|
|
184
|
+
for _, name := range field.Names {
|
|
185
|
+
fn.Params = append(fn.Params, name.Name)
|
|
186
|
+
fn.Types[name.Name] = typeKey
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if decl.Type.Results != nil {
|
|
191
|
+
for _, field := range decl.Type.Results.List {
|
|
192
|
+
typeKey := s.TypeKey(field.Type, file)
|
|
193
|
+
if len(field.Names) == 0 {
|
|
194
|
+
fn.Results = append(fn.Results, typeKey)
|
|
195
|
+
}
|
|
196
|
+
for range field.Names {
|
|
197
|
+
fn.Results = append(fn.Results, typeKey)
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
s.Functions[key] = fn
|
|
202
|
+
if receiver != "" {
|
|
203
|
+
s.Methods[receiver] = append(s.Methods[receiver], fn)
|
|
204
|
+
s.ByName[fn.Name] = append(s.ByName[fn.Name], fn)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Implements is whether a receiver type has every method an interface asks
|
|
209
|
+
// for, by name. Names are what a port is: a type with the right names is
|
|
210
|
+
// what the assembly hands over, and the compiler holds the signatures.
|
|
211
|
+
func (s *Index) Implements(receiver, iface string) bool {
|
|
212
|
+
want := s.Interfaces[iface]
|
|
213
|
+
if len(want) == 0 {
|
|
214
|
+
return false
|
|
215
|
+
}
|
|
216
|
+
have := map[string]bool{}
|
|
217
|
+
for _, fn := range s.Methods[receiver] {
|
|
218
|
+
have[fn.Name] = true
|
|
219
|
+
}
|
|
220
|
+
for _, name := range want {
|
|
221
|
+
if !have[name] {
|
|
222
|
+
return false
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return true
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// AssignedTo is the last expression a local name was given in the function,
|
|
229
|
+
// read once per function.
|
|
230
|
+
func (s *Index) AssignedTo(fn *Function, name string) (Assignment, bool) {
|
|
231
|
+
if fn.assigned == nil {
|
|
232
|
+
fn.assigned = map[string]Assignment{}
|
|
233
|
+
if fn.Decl.Body != nil {
|
|
234
|
+
ast.Inspect(fn.Decl.Body, func(node ast.Node) bool {
|
|
235
|
+
switch stmt := node.(type) {
|
|
236
|
+
case *ast.AssignStmt:
|
|
237
|
+
record(fn.assigned, stmt.Lhs, stmt.Rhs)
|
|
238
|
+
case *ast.ValueSpec:
|
|
239
|
+
var lhs []ast.Expr
|
|
240
|
+
for _, ident := range stmt.Names {
|
|
241
|
+
lhs = append(lhs, ident)
|
|
242
|
+
}
|
|
243
|
+
record(fn.assigned, lhs, stmt.Values)
|
|
244
|
+
}
|
|
245
|
+
return true
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
found, ok := fn.assigned[name]
|
|
250
|
+
return found, ok
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
func record(into map[string]Assignment, lhs, rhs []ast.Expr) {
|
|
254
|
+
if len(rhs) == 0 {
|
|
255
|
+
return
|
|
256
|
+
}
|
|
257
|
+
for i, target := range lhs {
|
|
258
|
+
ident, ok := target.(*ast.Ident)
|
|
259
|
+
if !ok || ident.Name == "_" {
|
|
260
|
+
continue
|
|
261
|
+
}
|
|
262
|
+
if len(rhs) == 1 && len(lhs) > 1 {
|
|
263
|
+
into[ident.Name] = Assignment{Expr: rhs[0], Index: i}
|
|
264
|
+
} else if i < len(rhs) {
|
|
265
|
+
into[ident.Name] = Assignment{Expr: rhs[i]}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// TypeOf is the type key of an expression, as far as declarations say: a
|
|
271
|
+
// parameter or receiver by its declared type, a local by what it was
|
|
272
|
+
// assigned, a field by its struct, a call by what the callee returns.
|
|
273
|
+
func (s *Index) TypeOf(expr ast.Expr, fn *Function) string {
|
|
274
|
+
switch value := Unwrap(expr).(type) {
|
|
275
|
+
case *ast.Ident:
|
|
276
|
+
if kind := fn.Types[value.Name]; kind != "" {
|
|
277
|
+
return kind
|
|
278
|
+
}
|
|
279
|
+
key := fn.Key + ":" + value.Name
|
|
280
|
+
if s.typing[key] {
|
|
281
|
+
return ""
|
|
282
|
+
}
|
|
283
|
+
s.typing[key] = true
|
|
284
|
+
defer delete(s.typing, key)
|
|
285
|
+
if given, ok := s.AssignedTo(fn, value.Name); ok {
|
|
286
|
+
if call, isCall := Unwrap(given.Expr).(*ast.CallExpr); isCall {
|
|
287
|
+
results := s.ResultsOf(call, fn)
|
|
288
|
+
if given.Index < len(results) {
|
|
289
|
+
return results[given.Index]
|
|
290
|
+
}
|
|
291
|
+
return ""
|
|
292
|
+
}
|
|
293
|
+
if given.Index == 0 {
|
|
294
|
+
return s.TypeOf(given.Expr, fn)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
case *ast.CompositeLit:
|
|
298
|
+
return s.TypeKey(value.Type, fn.File)
|
|
299
|
+
case *ast.SelectorExpr:
|
|
300
|
+
if st := s.Structs[s.TypeOf(value.X, fn)]; st != nil {
|
|
301
|
+
return st.Fields[value.Sel.Name]
|
|
302
|
+
}
|
|
303
|
+
case *ast.CallExpr:
|
|
304
|
+
if results := s.ResultsOf(value, fn); len(results) > 0 {
|
|
305
|
+
return results[0]
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return ""
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// ResultsOf is what a call returns: the callee's declared results when it is
|
|
312
|
+
// in the tree, KnownResults when it is a library's.
|
|
313
|
+
func (s *Index) ResultsOf(call *ast.CallExpr, fn *Function) []string {
|
|
314
|
+
for _, target := range s.Callees(call, fn) {
|
|
315
|
+
return target.Results
|
|
316
|
+
}
|
|
317
|
+
if key := s.ExternalKey(call, fn); key != "" {
|
|
318
|
+
return s.KnownResults[key]
|
|
319
|
+
}
|
|
320
|
+
return nil
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ExternalKey names a call on something declared outside the tree: a
|
|
324
|
+
// package function by import path, a method by the type of what it is
|
|
325
|
+
// called on.
|
|
326
|
+
func (s *Index) ExternalKey(call *ast.CallExpr, fn *Function) string {
|
|
327
|
+
sel, ok := call.Fun.(*ast.SelectorExpr)
|
|
328
|
+
if !ok {
|
|
329
|
+
return ""
|
|
330
|
+
}
|
|
331
|
+
if base, ok := sel.X.(*ast.Ident); ok {
|
|
332
|
+
if imported := fn.File.Imports[base.Name]; imported != "" && fn.Types[base.Name] == "" {
|
|
333
|
+
if _, local := s.AssignedTo(fn, base.Name); !local {
|
|
334
|
+
return imported + "." + sel.Sel.Name
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if recv := s.TypeOf(sel.X, fn); recv != "" {
|
|
339
|
+
return recv + "." + sel.Sel.Name
|
|
340
|
+
}
|
|
341
|
+
return ""
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Callees is every function in the tree a call may reach: the one named,
|
|
345
|
+
// when it is a plain function or a method on a known type; every
|
|
346
|
+
// implementation, when it is a method on an interface the tree declares.
|
|
347
|
+
func (s *Index) Callees(call *ast.CallExpr, fn *Function) []*Function {
|
|
348
|
+
return s.Targets(call.Fun, fn)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Targets is every function an expression names - the same as Callees, for
|
|
352
|
+
// a function value that is passed rather than called: `h.Handle` handed to a
|
|
353
|
+
// router, `consume` handed to a subscriber.
|
|
354
|
+
func (s *Index) Targets(expr ast.Expr, fn *Function) []*Function {
|
|
355
|
+
switch callee := Unwrap(expr).(type) {
|
|
356
|
+
case *ast.Ident:
|
|
357
|
+
if target := s.Functions[fn.File.Pkg+"."+callee.Name]; target != nil {
|
|
358
|
+
return []*Function{target}
|
|
359
|
+
}
|
|
360
|
+
case *ast.SelectorExpr:
|
|
361
|
+
if base, ok := callee.X.(*ast.Ident); ok && fn.Types[base.Name] == "" {
|
|
362
|
+
if imported := fn.File.Imports[base.Name]; imported != "" {
|
|
363
|
+
if _, local := s.AssignedTo(fn, base.Name); !local {
|
|
364
|
+
if target := s.Functions[imported+"."+callee.Sel.Name]; target != nil {
|
|
365
|
+
return []*Function{target}
|
|
366
|
+
}
|
|
367
|
+
return nil
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
recv := s.TypeOf(callee.X, fn)
|
|
372
|
+
if recv == "" {
|
|
373
|
+
return nil
|
|
374
|
+
}
|
|
375
|
+
if target := s.Functions[recv+"."+callee.Sel.Name]; target != nil {
|
|
376
|
+
return []*Function{target}
|
|
377
|
+
}
|
|
378
|
+
if s.Interfaces[recv] != nil {
|
|
379
|
+
var out []*Function
|
|
380
|
+
for _, candidate := range s.ByName[callee.Sel.Name] {
|
|
381
|
+
if s.Implements(candidate.Receiver, recv) {
|
|
382
|
+
out = append(out, candidate)
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
|
|
386
|
+
return out
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
return nil
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ParamIndex is which parameter a name is, or -1.
|
|
393
|
+
func ParamIndex(fn *Function, name string) int {
|
|
394
|
+
for i, param := range fn.Params {
|
|
395
|
+
if param == name {
|
|
396
|
+
return i
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return -1
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// SortedFunctions is every function with a body, in a fixed order.
|
|
403
|
+
func (s *Index) SortedFunctions() []*Function {
|
|
404
|
+
out := make([]*Function, 0, len(s.Functions))
|
|
405
|
+
for _, fn := range s.Functions {
|
|
406
|
+
if fn.Decl.Body != nil {
|
|
407
|
+
out = append(out, fn)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
sort.Slice(out, func(i, j int) bool { return out[i].Key < out[j].Key })
|
|
411
|
+
return out
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// SingleReturn is the expression a function is, when its whole body is
|
|
415
|
+
// `return x`: the Name() of an event, the Topic() of a message.
|
|
416
|
+
func SingleReturn(fn *Function) ast.Expr {
|
|
417
|
+
if fn.Decl.Body == nil || len(fn.Decl.Body.List) != 1 {
|
|
418
|
+
return nil
|
|
419
|
+
}
|
|
420
|
+
ret, ok := fn.Decl.Body.List[0].(*ast.ReturnStmt)
|
|
421
|
+
if !ok || len(ret.Results) != 1 {
|
|
422
|
+
return nil
|
|
423
|
+
}
|
|
424
|
+
return ret.Results[0]
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Resolve is the strings an expression can be worth: a literal; a constant,
|
|
428
|
+
// the tree's own or an imported one; a config field's default; a call to a
|
|
429
|
+
// function whose body is one return; a `string(x)` conversion of any of
|
|
430
|
+
// those; a concatenation of them; a parameter, by what every caller passes
|
|
431
|
+
// for it. Empty when it is none of those - never a guess.
|
|
432
|
+
func (s *Index) Resolve(expr ast.Expr, fn *Function, depth int, visiting map[string]bool) []Resolved {
|
|
433
|
+
at := s.At(expr.Pos())
|
|
434
|
+
switch value := Unwrap(expr).(type) {
|
|
435
|
+
case *ast.BasicLit:
|
|
436
|
+
if value.Kind == token.STRING {
|
|
437
|
+
text, _ := strconv.Unquote(value.Value)
|
|
438
|
+
return []Resolved{{Value: text, At: at}}
|
|
439
|
+
}
|
|
440
|
+
case *ast.Ident:
|
|
441
|
+
if index := ParamIndex(fn, value.Name); index >= 0 {
|
|
442
|
+
return s.FromCallers(fn, index, depth, visiting)
|
|
443
|
+
}
|
|
444
|
+
key := fn.Key + ":" + value.Name
|
|
445
|
+
if visiting[key] {
|
|
446
|
+
return nil
|
|
447
|
+
}
|
|
448
|
+
visiting[key] = true
|
|
449
|
+
defer delete(visiting, key)
|
|
450
|
+
if given, ok := s.AssignedTo(fn, value.Name); ok {
|
|
451
|
+
if given.Index == 0 {
|
|
452
|
+
return s.Resolve(given.Expr, fn, depth, visiting)
|
|
453
|
+
}
|
|
454
|
+
return nil
|
|
455
|
+
}
|
|
456
|
+
if text := s.StringOf(value, fn.File, nil); text != "" {
|
|
457
|
+
return []Resolved{{Value: text, At: at}}
|
|
458
|
+
}
|
|
459
|
+
case *ast.SelectorExpr:
|
|
460
|
+
if text := s.StringOf(value, fn.File, nil); text != "" {
|
|
461
|
+
return []Resolved{{Value: text, At: at}}
|
|
462
|
+
}
|
|
463
|
+
if st := s.Structs[s.TypeOf(value.X, fn)]; st != nil {
|
|
464
|
+
if def := st.Defaults[value.Sel.Name]; def != "" {
|
|
465
|
+
return []Resolved{{Value: def, At: at}}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
case *ast.BinaryExpr:
|
|
469
|
+
if value.Op != token.ADD {
|
|
470
|
+
return nil
|
|
471
|
+
}
|
|
472
|
+
return s.concat(value.X, value.Y, fn, depth, visiting)
|
|
473
|
+
case *ast.CallExpr:
|
|
474
|
+
if ident, ok := value.Fun.(*ast.Ident); ok && ident.Name == "string" && len(value.Args) == 1 && s.Functions[fn.File.Pkg+".string"] == nil {
|
|
475
|
+
return s.Resolve(value.Args[0], fn, depth, visiting)
|
|
476
|
+
}
|
|
477
|
+
var out []Resolved
|
|
478
|
+
for _, target := range s.Callees(value, fn) {
|
|
479
|
+
if ret := SingleReturn(target); ret != nil {
|
|
480
|
+
key := "call:" + target.Key
|
|
481
|
+
if visiting[key] {
|
|
482
|
+
continue
|
|
483
|
+
}
|
|
484
|
+
visiting[key] = true
|
|
485
|
+
out = append(out, s.Resolve(ret, target, depth, visiting)...)
|
|
486
|
+
delete(visiting, key)
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
return out
|
|
490
|
+
}
|
|
491
|
+
return nil
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// concat is every string `x + y` can be, when both halves resolve; a half
|
|
495
|
+
// that does not leaves the whole unresolved rather than half-guessed.
|
|
496
|
+
func (s *Index) concat(x, y ast.Expr, fn *Function, depth int, visiting map[string]bool) []Resolved {
|
|
497
|
+
left := s.Resolve(x, fn, depth, visiting)
|
|
498
|
+
if len(left) == 0 {
|
|
499
|
+
return nil
|
|
500
|
+
}
|
|
501
|
+
right := s.Resolve(y, fn, depth, visiting)
|
|
502
|
+
if len(right) == 0 {
|
|
503
|
+
return nil
|
|
504
|
+
}
|
|
505
|
+
var out []Resolved
|
|
506
|
+
for _, a := range left {
|
|
507
|
+
for _, b := range right {
|
|
508
|
+
out = append(out, Resolved{Value: a.Value + b.Value, At: a.At, Name: FirstNonEmpty(a.Name, b.Name)})
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return out
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// FromCallers is what a parameter is worth: whatever each call site passes
|
|
515
|
+
// for it, resolved there. When Companion names another parameter, a call
|
|
516
|
+
// site that passes a literal or constant for it is taken to name the
|
|
517
|
+
// message.
|
|
518
|
+
func (s *Index) FromCallers(fn *Function, index, depth int, visiting map[string]bool) []Resolved {
|
|
519
|
+
if depth >= s.Hops {
|
|
520
|
+
return nil
|
|
521
|
+
}
|
|
522
|
+
key := fn.Key + "#" + strconv.Itoa(index)
|
|
523
|
+
if visiting[key] {
|
|
524
|
+
return nil
|
|
525
|
+
}
|
|
526
|
+
visiting[key] = true
|
|
527
|
+
defer delete(visiting, key)
|
|
528
|
+
|
|
529
|
+
nameIndex := -1
|
|
530
|
+
if s.Companion != nil {
|
|
531
|
+
nameIndex = s.Companion(fn, index)
|
|
532
|
+
}
|
|
533
|
+
var out []Resolved
|
|
534
|
+
for _, arg := range s.ArgsFromCallers(fn, index) {
|
|
535
|
+
found := s.Resolve(arg.Expr, arg.Fn, depth+1, visiting)
|
|
536
|
+
if nameIndex >= 0 && nameIndex < len(arg.Call.Args) {
|
|
537
|
+
names := s.Resolve(arg.Call.Args[nameIndex], arg.Fn, depth+1, visiting)
|
|
538
|
+
if len(names) == 1 {
|
|
539
|
+
for i := range found {
|
|
540
|
+
found[i].Name = names[0].Value
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
out = append(out, found...)
|
|
545
|
+
}
|
|
546
|
+
return out
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ArgsFromCallers is every expression a caller passes for fn's parameter at
|
|
550
|
+
// index, each with the function it was written in. In a fixed order.
|
|
551
|
+
func (s *Index) ArgsFromCallers(fn *Function, index int) []CallArg {
|
|
552
|
+
var out []CallArg
|
|
553
|
+
for _, site := range s.CallSites(fn) {
|
|
554
|
+
if index < len(site.Call.Args) {
|
|
555
|
+
out = append(out, CallArg{Expr: site.Call.Args[index], Fn: site.Fn, Call: site.Call})
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return out
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// CallSites is every call in the tree that lands on fn, with the function
|
|
562
|
+
// it is written in. In a fixed order. Expr is nil: the site is the call.
|
|
563
|
+
func (s *Index) CallSites(fn *Function) []CallArg {
|
|
564
|
+
var out []CallArg
|
|
565
|
+
for _, caller := range s.SortedFunctions() {
|
|
566
|
+
ast.Inspect(caller.Decl.Body, func(node ast.Node) bool {
|
|
567
|
+
call, ok := node.(*ast.CallExpr)
|
|
568
|
+
if !ok || !s.Reaches(call, caller, fn) {
|
|
569
|
+
return true
|
|
570
|
+
}
|
|
571
|
+
out = append(out, CallArg{Fn: caller, Call: call})
|
|
572
|
+
return true
|
|
573
|
+
})
|
|
574
|
+
}
|
|
575
|
+
return out
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Reaches is whether a call lands on fn: directly, or through an interface
|
|
579
|
+
// fn's receiver satisfies.
|
|
580
|
+
func (s *Index) Reaches(call *ast.CallExpr, caller, fn *Function) bool {
|
|
581
|
+
for _, target := range s.Callees(call, caller) {
|
|
582
|
+
if target == fn {
|
|
583
|
+
return true
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return false
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// TypesOf is every concrete type an expression can have: TypeOf, or when the
|
|
590
|
+
// expression is a parameter of an interface or unnamed type, the types the
|
|
591
|
+
// callers pass for it, followed Hops levels up. Empty when nothing says.
|
|
592
|
+
func (s *Index) TypesOf(expr ast.Expr, fn *Function, depth int, visiting map[string]bool) []string {
|
|
593
|
+
if ident, ok := Unwrap(expr).(*ast.Ident); ok {
|
|
594
|
+
if index := ParamIndex(fn, ident.Name); index >= 0 && !s.isConcrete(fn.Types[ident.Name]) {
|
|
595
|
+
if depth >= s.Hops {
|
|
596
|
+
return nil
|
|
597
|
+
}
|
|
598
|
+
key := fn.Key + "#type:" + strconv.Itoa(index)
|
|
599
|
+
if visiting[key] {
|
|
600
|
+
return nil
|
|
601
|
+
}
|
|
602
|
+
visiting[key] = true
|
|
603
|
+
defer delete(visiting, key)
|
|
604
|
+
var out []string
|
|
605
|
+
seen := map[string]bool{}
|
|
606
|
+
for _, arg := range s.ArgsFromCallers(fn, index) {
|
|
607
|
+
for _, found := range s.TypesOf(arg.Expr, arg.Fn, depth+1, visiting) {
|
|
608
|
+
if !seen[found] {
|
|
609
|
+
seen[found] = true
|
|
610
|
+
out = append(out, found)
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return out
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if found := s.TypeOf(expr, fn); s.isConcrete(found) {
|
|
618
|
+
return []string{found}
|
|
619
|
+
}
|
|
620
|
+
return nil
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// isConcrete is whether a type key names a non-interface type the tree
|
|
624
|
+
// declares. A library's interface - river.JobArgs - is not declared here and
|
|
625
|
+
// so is not concrete either: a parameter of that type says nothing about
|
|
626
|
+
// which job it carries, and the callers are asked.
|
|
627
|
+
func (s *Index) isConcrete(key string) bool {
|
|
628
|
+
return s.Named[key] && s.Interfaces[key] == nil
|
|
629
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
package goscan
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strings"
|
|
5
|
+
"testing"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
func TestIndexSharesASTAndResolvesRealLocalPackageNames(t *testing.T) {
|
|
9
|
+
root := t.TempDir()
|
|
10
|
+
write(t, root, "go.mod", "module example.com/index\n")
|
|
11
|
+
write(t, root, "main.go", `package main
|
|
12
|
+
import "example.com/index/adapter/v2"
|
|
13
|
+
const Topic = wire.Topic
|
|
14
|
+
`)
|
|
15
|
+
write(t, root, "adapter/v2/messages.go", "package wire\nconst Topic = \"orders\"\n")
|
|
16
|
+
index, err := ReadWithOptions(root, ReadOptions{IncludeGenerated: true})
|
|
17
|
+
if err != nil {
|
|
18
|
+
t.Fatal(err)
|
|
19
|
+
}
|
|
20
|
+
files := index.PackageFiles(".")
|
|
21
|
+
if len(files) != 1 || files[0].Imports["wire"] != "example.com/index/adapter/v2" {
|
|
22
|
+
t.Fatalf("imports = %+v", files)
|
|
23
|
+
}
|
|
24
|
+
if index.ConstantString("example.com/index.Topic", nil) != "orders" {
|
|
25
|
+
t.Fatal("real import name did not resolve")
|
|
26
|
+
}
|
|
27
|
+
again, err := PackageIndex(root, ".", index)
|
|
28
|
+
if err != nil || again.PackageFiles(".")[0].Node != files[0].Node || again.Fset != index.Fset {
|
|
29
|
+
t.Fatal("AST was reparsed")
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
func TestBuildSelectionGeneratedCodeAndModuleBoundaries(t *testing.T) {
|
|
34
|
+
root := t.TempDir()
|
|
35
|
+
write(t, root, "go.mod", "module example.com/index\n")
|
|
36
|
+
write(t, root, "app_linux.go", "package app\n")
|
|
37
|
+
write(t, root, "app_windows.go", "package app\n")
|
|
38
|
+
write(t, root, "feature.go", "//go:build custom\n\npackage app\n")
|
|
39
|
+
write(t, root, "client.go", "// Code generated by fixture. DO NOT EDIT.\npackage app\n")
|
|
40
|
+
write(t, root, "child/go.mod", "module example.com/child\n")
|
|
41
|
+
write(t, root, "child/app.go", "package child\n")
|
|
42
|
+
write(t, root, "testdata/invalid.go", "not Go source")
|
|
43
|
+
index, err := ReadWithOptions(root, ReadOptions{GOOS: "linux", GOARCH: "amd64", IncludeGenerated: true, Tags: []string{"custom"}})
|
|
44
|
+
if err != nil {
|
|
45
|
+
t.Fatal(err)
|
|
46
|
+
}
|
|
47
|
+
if len(index.Files) != 3 {
|
|
48
|
+
t.Fatalf("files = %+v", index.Files)
|
|
49
|
+
}
|
|
50
|
+
generated := 0
|
|
51
|
+
for _, file := range index.Files {
|
|
52
|
+
if file.Generated {
|
|
53
|
+
generated++
|
|
54
|
+
}
|
|
55
|
+
if strings.Contains(file.Name, "windows") || strings.HasPrefix(file.Name, "child/") {
|
|
56
|
+
t.Fatalf("foreign source selected: %s", file.Name)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if generated != 1 {
|
|
60
|
+
t.Fatalf("generated = %d", generated)
|
|
61
|
+
}
|
|
62
|
+
syntax, err := ReadWithOptions(root, ReadOptions{GOOS: "linux", GOARCH: "amd64"})
|
|
63
|
+
if err != nil || len(syntax.Files) != 1 || syntax.Files[0].Name != "app_linux.go" {
|
|
64
|
+
t.Fatalf("syntax source selection: %+v / %v", syntax, err)
|
|
65
|
+
}
|
|
66
|
+
}
|