Skip to content

Commit a12e88e

Browse files
authored
Add support for LSIF Typed (#732)
* Add support for LSIF Typed Running `src lsif upload` now converts LSIF Typed into LSIF Graph in the following scenarios: - When the provided `-file` flag matches the file extension ".lsif-typed" - When the provided `-file` flag points to a non-existent file, while the sibling `*.lsif-typed` file exists. Fixes https://github.com/sourcegraph/sourcegraph/issues/27405 * Fix ordering of how we initialize `*output.Output` * Add test cases for `handleLSIFTyped` function. * Remove redundant blank line * Use `t.TempDir()` to create temporary directory
1 parent c5c6f21 commit a12e88e

5 files changed

Lines changed: 172 additions & 24 deletions

File tree

cmd/src/lsif_upload.go

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,7 @@ Examples:
5757
func handleLSIFUpload(args []string) error {
5858
ctx := context.Background()
5959

60-
err := parseAndValidateLSIFUploadFlags(args)
61-
out := lsifUploadOutput()
60+
out, err := parseAndValidateLSIFUploadFlags(args)
6261
if !lsifUploadFlags.json {
6362
if out != nil {
6463
printInferredArguments(out)
@@ -119,22 +118,6 @@ func handleLSIFUpload(args []string) error {
119118
return nil
120119
}
121120

122-
// lsifUploadOutput returns an output object that should be used to print the progres
123-
// of requests made during this upload. If -json, -no-progress, or -trace>0 is given,
124-
// then no output object is defined.
125-
//
126-
// For -no-progress and -trace>0 conditions, emergency loggers will be used to display
127-
// inferred arguments and the URL at which processing status is shown.
128-
func lsifUploadOutput() (out *output.Output) {
129-
if lsifUploadFlags.json || lsifUploadFlags.noProgress || lsifUploadFlags.verbosity > 0 {
130-
return nil
131-
}
132-
133-
return output.NewOutput(flag.CommandLine.Output(), output.OutputOpts{
134-
Verbose: true,
135-
})
136-
}
137-
138121
// lsifUploadOptions creates a set of upload options given the values in the flags.
139122
func lsifUploadOptions(out *output.Output) upload.UploadOptions {
140123
var associatedIndexID *int

cmd/src/lsif_upload_flags.go

Lines changed: 92 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import (
77
"strings"
88

99
"github.com/sourcegraph/sourcegraph/lib/errors"
10+
"github.com/sourcegraph/sourcegraph/lib/output"
11+
"google.golang.org/protobuf/proto"
1012

13+
"github.com/sourcegraph/sourcegraph/lib/codeintel/lsif/protocol/reader"
14+
"github.com/sourcegraph/sourcegraph/lib/codeintel/lsiftyped"
1115
"github.com/sourcegraph/sourcegraph/lib/codeintel/upload"
1216

1317
"github.com/sourcegraph/src-cli/internal/api"
@@ -84,11 +88,13 @@ func init() {
8488
//
8589
// On success, the global lsifUploadFlags object will be populated with valid values. An
8690
// error is returned on failure.
87-
func parseAndValidateLSIFUploadFlags(args []string) error {
91+
func parseAndValidateLSIFUploadFlags(args []string) (*output.Output, error) {
8892
if err := lsifUploadFlagSet.Parse(args); err != nil {
89-
return err
93+
return nil, err
9094
}
9195

96+
out := lsifUploadOutput()
97+
9298
// extract only the -insecure-skip-verify flag so we dont get 'flag provided but not defined'
9399
var insecureSkipVerifyFlag []string
94100
for _, s := range args {
@@ -102,11 +108,15 @@ func parseAndValidateLSIFUploadFlags(args []string) error {
102108
// and maybe we'll use some in the future
103109
lsifUploadFlags.apiFlags = api.NewFlags(apiClientFlagSet)
104110
if err := apiClientFlagSet.Parse(insecureSkipVerifyFlag); err != nil {
105-
return err
111+
return nil, err
112+
}
113+
114+
if err := handleLSIFTyped(out); err != nil {
115+
return nil, err
106116
}
107117

108118
if inferenceErrors := inferMissingLSIFUploadFlags(); len(inferenceErrors) > 0 {
109-
return errorWithHint{
119+
return nil, errorWithHint{
110120
err: inferenceErrors[0].err, hint: strings.Join([]string{
111121
fmt.Sprintf(
112122
"Unable to determine %s from environment. Check your working directory or supply -%s={value} explicitly",
@@ -118,17 +128,93 @@ func parseAndValidateLSIFUploadFlags(args []string) error {
118128
}
119129

120130
if err := validateLSIFUploadFlags(); err != nil {
121-
return err
131+
return nil, err
122132
}
123133

124-
return nil
134+
return out, nil
135+
}
136+
137+
// lsifUploadOutput returns an output object that should be used to print the progres
138+
// of requests made during this upload. If -json, -no-progress, or -trace>0 is given,
139+
// then no output object is defined.
140+
//
141+
// For -no-progress and -trace>0 conditions, emergency loggers will be used to display
142+
// inferred arguments and the URL at which processing status is shown.
143+
func lsifUploadOutput() (out *output.Output) {
144+
if lsifUploadFlags.json || lsifUploadFlags.noProgress || lsifUploadFlags.verbosity > 0 {
145+
return nil
146+
}
147+
148+
return output.NewOutput(flag.CommandLine.Output(), output.OutputOpts{
149+
Verbose: true,
150+
})
125151
}
126152

127153
type argumentInferenceError struct {
128154
argument string
129155
err error
130156
}
131157

158+
func handleLSIFTyped(out *output.Output) error {
159+
if strings.HasSuffix(lsifUploadFlags.file, ".lsif-typed") {
160+
// The user explicitly passed in a -file flag that points to an LSIF Typed index.
161+
inputFile := lsifUploadFlags.file
162+
outputFile := strings.TrimSuffix(inputFile, "-typed")
163+
lsifUploadFlags.file = outputFile
164+
return convertLSIFTypedToLSIFGraph(out, inputFile, outputFile)
165+
}
166+
167+
if _, err := os.Stat(lsifUploadFlags.file); err == nil {
168+
// Do nothing, the provided -flag flag points to an existing
169+
// file that does not have the file extension `*.lsif-typed`.
170+
return nil
171+
}
172+
173+
lsifTypedFile := lsifUploadFlags.file + "-typed"
174+
if _, err := os.Stat(lsifTypedFile); os.IsNotExist(err) {
175+
// The inferred path of the sibling `*.lsif-typed` file does not exist.
176+
return nil
177+
}
178+
179+
// The provided -file flag points to an `*.lsif` file that doesn't exist
180+
// so we convert the sibling `*.lsif-typed` file (which we confirmed exists).
181+
return convertLSIFTypedToLSIFGraph(out, lsifTypedFile, lsifUploadFlags.file)
182+
}
183+
184+
// Reads the LSIF Typed encoded input file and writes the corresponding LSIF
185+
// Graph encoded output file.
186+
func convertLSIFTypedToLSIFGraph(out *output.Output, inputFile, outputFile string) error {
187+
out.Writef("%s Converting %s into %s", output.EmojiInfo, inputFile, outputFile)
188+
tmp, err := os.Create(outputFile)
189+
if err != nil {
190+
return err
191+
}
192+
defer tmp.Close()
193+
194+
data, err := os.ReadFile(inputFile)
195+
if err != nil {
196+
panic(err)
197+
}
198+
index := lsiftyped.Index{}
199+
err = proto.Unmarshal(data, &index)
200+
if err != nil {
201+
panic(errors.Wrapf(err, "failed to parse protobuf file '%s'", inputFile))
202+
}
203+
els, err := reader.ConvertTypedIndexToGraphIndex(&index)
204+
if err != nil {
205+
panic(errors.Wrapf(err, "failed reader.ConvertTypedIndexToGraphIndex"))
206+
}
207+
err = reader.WriteNDJSON(reader.ElementsToJsonElements(els), tmp)
208+
if err != nil {
209+
panic(err)
210+
}
211+
err = tmp.Close()
212+
if err != nil {
213+
return err
214+
}
215+
return nil
216+
}
217+
132218
// inferMissingLSIFUploadFlags updates the flags values which were not explicitly
133219
// supplied by the user with default values inferred from the current git state and
134220
// filesystem.

cmd/src/lsif_upload_flags_test.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"testing"
7+
8+
"github.com/sourcegraph/sourcegraph/lib/codeintel/lsiftyped"
9+
"google.golang.org/protobuf/proto"
10+
)
11+
12+
var exampleLsifTypedIndex = lsiftyped.Index{
13+
Metadata: &lsiftyped.Metadata{
14+
TextDocumentEncoding: lsiftyped.TextEncoding_UTF8,
15+
ToolInfo: &lsiftyped.ToolInfo{
16+
Name: "hello",
17+
Version: "1.0.0",
18+
},
19+
},
20+
}
21+
22+
var exampleLsifGraphString = `{"id":1,"version":"0.4.3","positionEncoding":"utf-8","toolInfo":{"name":"hello","version":"1.0.0"},"type":"vertex","label":"metaData"}
23+
`
24+
25+
func exampleLsifTypedBytes(t *testing.T) []byte {
26+
bytes, err := proto.Marshal(&exampleLsifTypedIndex)
27+
if err != nil {
28+
t.Fatal(err)
29+
}
30+
return bytes
31+
}
32+
33+
func createTempLsifTypedFile(t *testing.T) (typedFile, graphFile string) {
34+
dir := t.TempDir()
35+
typedFile = filepath.Join(dir, "dump.lsif-typed")
36+
graphFile = filepath.Join(dir, "dump.lsif")
37+
err := os.WriteFile(typedFile, exampleLsifTypedBytes(t), 0755)
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
return typedFile, graphFile
42+
}
43+
44+
func assertLsifGraphOutput(t *testing.T, lsifGraphFile, expectedGraphString string) {
45+
out := lsifUploadOutput()
46+
handleLSIFTyped(out)
47+
lsifGraph, err := os.ReadFile(lsifGraphFile)
48+
if err != nil {
49+
t.Fatal(err)
50+
}
51+
obtained := string(lsifGraph)
52+
if obtained != expectedGraphString {
53+
t.Fatalf("unexpected LSIF output %s", obtained)
54+
}
55+
if lsifGraphFile != lsifUploadFlags.file {
56+
t.Fatalf("unexpected lsifUploadFlag.file value %s, expected %s", lsifUploadFlags.file, lsifGraphFile)
57+
}
58+
}
59+
60+
func TestImplicitlyConvertLsifTypedIntoGraph(t *testing.T) {
61+
_, graphFile := createTempLsifTypedFile(t)
62+
lsifUploadFlags.file = graphFile
63+
assertLsifGraphOutput(t, graphFile, exampleLsifGraphString)
64+
}
65+
66+
func TestImplicitlyIgnoreLsifTyped(t *testing.T) {
67+
_, graphFile := createTempLsifTypedFile(t)
68+
lsifUploadFlags.file = graphFile
69+
os.WriteFile(graphFile, []byte("hello world"), 0755)
70+
assertLsifGraphOutput(t, graphFile, "hello world")
71+
}
72+
73+
func TestExplicitlyConvertLsifTypedIntoGraph(t *testing.T) {
74+
typedFile, graphFile := createTempLsifTypedFile(t)
75+
lsifUploadFlags.file = typedFile
76+
assertLsifGraphOutput(t, graphFile, exampleLsifGraphString)
77+
}

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ require (
2222
github.com/sourcegraph/sourcegraph/lib v0.0.0-20220414150621-eeb00fcedd88
2323
github.com/stretchr/testify v1.7.1
2424
golang.org/x/net v0.0.0-20220325170049-de3da57026de
25+
google.golang.org/protobuf v1.27.1
2526
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
2627
jaytaylor.com/html2text v0.0.0-20200412013138-3577fbdbcff7
2728
)

go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
491491
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
492492
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
493493
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
494+
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
494495
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
495496
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
496497
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

0 commit comments

Comments
 (0)