@@ -24,6 +24,7 @@ import (
24
24
"google.golang.org/protobuf/types/descriptorpb"
25
25
26
26
"github.com/jhump/protoreflect/desc"
27
+ "github.com/jhump/protoreflect/desc/internal"
27
28
"github.com/jhump/protoreflect/desc/protoparse/ast"
28
29
)
29
30
@@ -155,8 +156,15 @@ func (p Parser) ParseFiles(filenames ...string) ([]*desc.FileDescriptor, error)
155
156
return nil , err
156
157
}
157
158
// then we can infer import paths
158
- // TODO: if this re-writes one of the names in filenames, lookups below will break
159
- results = fixupFilenames (results )
159
+ var rewritten map [string ]string
160
+ results , rewritten = fixupFilenames (results )
161
+ if len (rewritten ) > 0 {
162
+ for i := range filenames {
163
+ if replace , ok := rewritten [filenames [i ]]; ok {
164
+ filenames [i ] = replace
165
+ }
166
+ }
167
+ }
160
168
resolverFromResults := protocompile .ResolverFunc (func (path string ) (protocompile.SearchResult , error ) {
161
169
res , ok := results [path ]
162
170
if ! ok {
@@ -244,7 +252,15 @@ func (p Parser) ParseFilesButDoNotLink(filenames ...string) ([]*descriptorpb.Fil
244
252
for _ , res := range results {
245
253
resultsMap [res .FileDescriptorProto ().GetName ()] = res
246
254
}
247
- resultsMap = fixupFilenames (resultsMap )
255
+ var rewritten map [string ]string
256
+ resultsMap , rewritten = fixupFilenames (resultsMap )
257
+ if len (rewritten ) > 0 {
258
+ for i := range filenames {
259
+ if replace , ok := rewritten [filenames [i ]]; ok {
260
+ filenames [i ] = replace
261
+ }
262
+ }
263
+ }
248
264
for i := range filenames {
249
265
results [i ] = resultsMap [filenames [i ]]
250
266
}
@@ -362,48 +378,100 @@ func parseToProtos(res protocompile.Resolver, filenames []string, rep *reporter.
362
378
func parseToProtosRecursive (res protocompile.Resolver , filenames []string , rep * reporter.Handler , srcPosAddr * SourcePos ) (map [string ]parser.Result , error ) {
363
379
results := make (map [string ]parser.Result , len (filenames ))
364
380
for _ , filename := range filenames {
365
- parseToProtoRecursive (res , filename , rep , srcPosAddr , results )
381
+ if err := parseToProtoRecursive (res , filename , rep , srcPosAddr , results ); err != nil {
382
+ return results , err
383
+ }
366
384
}
367
385
return results , rep .Error ()
368
386
}
369
387
370
- func parseToProtoRecursive (res protocompile.Resolver , filename string , rep * reporter.Handler , srcPosAddr * SourcePos , results map [string ]parser.Result ) {
388
+ func parseToProtoRecursive (res protocompile.Resolver , filename string , rep * reporter.Handler , srcPosAddr * SourcePos , results map [string ]parser.Result ) error {
371
389
if _ , ok := results [filename ]; ok {
372
390
// already processed this one
373
- return
391
+ return nil
374
392
}
375
393
results [filename ] = nil // placeholder entry
376
394
377
- astRoot , parseResult , _ := parseToAST (res , filename , rep )
378
- if rep . ReporterError () != nil {
379
- return
395
+ astRoot , parseResult , err := parseToAST (res , filename , rep )
396
+ if err != nil {
397
+ return err
380
398
}
381
399
if parseResult == nil {
382
- parseResult , _ = parser .ResultFromAST (astRoot , true , rep )
383
- if rep . ReporterError () != nil {
384
- return
400
+ parseResult , err = parser .ResultFromAST (astRoot , true , rep )
401
+ if err != nil {
402
+ return err
385
403
}
386
404
}
387
405
results [filename ] = parseResult
388
406
389
- for _ , decl := range astRoot .Decls {
390
- imp , ok := decl .(* ast2.ImportNode )
391
- if ! ok {
392
- continue
407
+ if astRoot != nil {
408
+ // We have an AST, so we use it to recursively examine imports.
409
+ for _ , decl := range astRoot .Decls {
410
+ imp , ok := decl .(* ast2.ImportNode )
411
+ if ! ok {
412
+ continue
413
+ }
414
+ err := func () error {
415
+ orig := * srcPosAddr
416
+ * srcPosAddr = astRoot .NodeInfo (imp .Name ).Start ()
417
+ defer func () {
418
+ * srcPosAddr = orig
419
+ }()
420
+
421
+ return parseToProtoRecursive (res , imp .Name .AsString (), rep , srcPosAddr , results )
422
+ }()
423
+ if err != nil {
424
+ return err
425
+ }
393
426
}
394
- func () {
427
+ return nil
428
+ }
429
+
430
+ // Without an AST, we must recursively examine the proto. This makes it harder
431
+ // (but not necessarily impossible) to get the source location of the import.
432
+ fd := parseResult .FileDescriptorProto ()
433
+ for i , dep := range fd .Dependency {
434
+ path := []int32 {internal .File_dependencyTag , int32 (i )}
435
+ err := func () error {
395
436
orig := * srcPosAddr
396
- * srcPosAddr = astRoot .NodeInfo (imp .Name ).Start ()
437
+ found := false
438
+ for _ , loc := range fd .GetSourceCodeInfo ().GetLocation () {
439
+ if pathsEqual (loc .Path , path ) {
440
+ * srcPosAddr = SourcePos {
441
+ Filename : dep ,
442
+ Line : int (loc .Span [0 ]),
443
+ Col : int (loc .Span [1 ]),
444
+ }
445
+ found = true
446
+ break
447
+ }
448
+ }
449
+ if ! found {
450
+ * srcPosAddr = * ast .UnknownPos (dep )
451
+ }
397
452
defer func () {
398
453
* srcPosAddr = orig
399
454
}()
400
455
401
- parseToProtoRecursive (res , imp . Name . AsString () , rep , srcPosAddr , results )
456
+ return parseToProtoRecursive (res , dep , rep , srcPosAddr , results )
402
457
}()
403
- if rep . ReporterError () != nil {
404
- return
458
+ if err != nil {
459
+ return err
405
460
}
406
461
}
462
+ return nil
463
+ }
464
+
465
+ func pathsEqual (a , b []int32 ) bool {
466
+ if len (a ) != len (b ) {
467
+ return false
468
+ }
469
+ for i := range a {
470
+ if a [i ] != b [i ] {
471
+ return false
472
+ }
473
+ }
474
+ return true
407
475
}
408
476
409
477
func newReporter (errRep ErrorReporter , warnRep WarningReporter ) reporter.Reporter {
@@ -487,11 +555,12 @@ func (p Parser) getResolver(filenames []string) (protocompile.Resolver, *SourceP
487
555
}, & srcPos
488
556
}
489
557
490
- func fixupFilenames (protos map [string ]parser.Result ) map [string ]parser.Result {
558
+ func fixupFilenames (protos map [string ]parser.Result ) ( revisedProtos map [string ]parser.Result , rewrittenPaths map [ string ] string ) {
491
559
// In the event that the given filenames (keys in the supplied map) do not
492
560
// match the actual paths used in 'import' statements in the files, we try
493
561
// to revise names in the protos so that they will match and be linkable.
494
- revisedProtos := map [string ]parser.Result {}
562
+ revisedProtos = make (map [string ]parser.Result , len (protos ))
563
+ rewrittenPaths = make (map [string ]string , len (protos ))
495
564
496
565
protoPaths := map [string ]struct {}{}
497
566
// TODO: this is O(n^2) but could likely be O(n) with a clever data structure (prefix tree that is indexed backwards?)
@@ -501,7 +570,7 @@ func fixupFilenames(protos map[string]parser.Result) map[string]parser.Result {
501
570
candidatesAvailable [name ] = struct {}{}
502
571
for _ , f := range protos {
503
572
for _ , imp := range f .FileDescriptorProto ().Dependency {
504
- if strings .HasSuffix (name , imp ) {
573
+ if strings .HasSuffix (name , imp ) || strings . HasSuffix ( imp , name ) {
505
574
candidates := importCandidates [imp ]
506
575
if candidates == nil {
507
576
candidates = map [string ]struct {}{}
@@ -529,37 +598,62 @@ func fixupFilenames(protos map[string]parser.Result) map[string]parser.Result {
529
598
if best == "" {
530
599
best = c
531
600
} else {
532
- // HACK: we can't actually tell which files is supposed to match
533
- // this import, so arbitrarily pick the "shorter" one (fewest
534
- // path elements) or, on a tie, the lexically earlier one
601
+ // NB: We can't actually tell which file is supposed to match
602
+ // this import. So we prefer the longest name. On a tie, we
603
+ // choose the lexically earliest match.
535
604
minLen := strings .Count (best , string (filepath .Separator ))
536
605
cLen := strings .Count (c , string (filepath .Separator ))
537
- if cLen < minLen || (cLen == minLen && c < best ) {
606
+ if cLen > minLen || (cLen == minLen && c < best ) {
538
607
best = c
539
608
}
540
609
}
541
610
}
542
611
if best != "" {
543
- prefix := best [: len (best )- len (imp )]
544
- if len (prefix ) > 0 {
612
+ if len (best ) > len (imp ) {
613
+ prefix := best [: len (best ) - len ( imp )]
545
614
protoPaths [prefix ] = struct {}{}
546
615
}
547
616
f := protos [best ]
548
617
f .FileDescriptorProto ().Name = proto .String (imp )
549
618
revisedProtos [imp ] = f
619
+ rewrittenPaths [best ] = imp
550
620
delete (candidatesAvailable , best )
621
+
622
+ // If other candidates are actually references to the same file, remove them.
623
+ for c := range candidates {
624
+ if _ , ok := candidatesAvailable [c ]; ! ok {
625
+ // already used this candidate and re-written its filename accordingly
626
+ continue
627
+ }
628
+ possibleDup := protos [c ]
629
+ prevName := possibleDup .FileDescriptorProto ().Name
630
+ possibleDup .FileDescriptorProto ().Name = proto .String (imp )
631
+ if ! proto .Equal (f .FileDescriptorProto (), protos [c ].FileDescriptorProto ()) {
632
+ // not equal: restore name and look at next one
633
+ possibleDup .FileDescriptorProto ().Name = prevName
634
+ continue
635
+ }
636
+ // This file used a different name but was actually the same file. So
637
+ // we prune it from the set.
638
+ rewrittenPaths [c ] = imp
639
+ delete (candidatesAvailable , c )
640
+ if len (c ) > len (imp ) {
641
+ prefix := c [:len (c )- len (imp )]
642
+ protoPaths [prefix ] = struct {}{}
643
+ }
644
+ }
551
645
}
552
646
}
553
647
554
648
if len (candidatesAvailable ) == 0 {
555
- return revisedProtos
649
+ return revisedProtos , rewrittenPaths
556
650
}
557
651
558
652
if len (protoPaths ) == 0 {
559
653
for c := range candidatesAvailable {
560
654
revisedProtos [c ] = protos [c ]
561
655
}
562
- return revisedProtos
656
+ return revisedProtos , rewrittenPaths
563
657
}
564
658
565
659
// Any remaining candidates are entry-points (not imported by others), so
@@ -588,12 +682,13 @@ func fixupFilenames(protos map[string]parser.Result) map[string]parser.Result {
588
682
f .FileDescriptorProto ().Name = proto .String (imp )
589
683
f .FileNode ()
590
684
revisedProtos [imp ] = f
685
+ rewrittenPaths [c ] = imp
591
686
} else {
592
687
revisedProtos [c ] = protos [c ]
593
688
}
594
689
}
595
690
596
- return revisedProtos
691
+ return revisedProtos , rewrittenPaths
597
692
}
598
693
599
694
func removeDynamicExtensions (fd protoreflect.FileDescriptor , alreadySeen map [string ]struct {}) {
0 commit comments