@@ -488,24 +488,45 @@ export class GoDebugConfigurationProvider implements vscode.DebugConfigurationPr
488
488
debugConfiguration [ 'env' ] = Object . assign ( goToolsEnvVars , fileEnvs , env ) ;
489
489
debugConfiguration [ 'envFile' ] = undefined ; // unset, since we already processed.
490
490
491
- const entriesWithRelativePaths = [ 'cwd' , 'output' , 'program' ] . filter (
492
- ( attr ) => debugConfiguration [ attr ] && ! path . isAbsolute ( debugConfiguration [ attr ] )
493
- ) ;
494
491
if ( debugAdapter === 'dlv-dap' ) {
495
- // 1. Relative paths -> absolute paths
496
- if ( entriesWithRelativePaths . length > 0 ) {
497
- const workspaceRoot = folder ?. uri . fsPath ;
498
- if ( workspaceRoot ) {
499
- entriesWithRelativePaths . forEach ( ( attr ) => {
500
- debugConfiguration [ attr ] = path . join ( workspaceRoot , debugConfiguration [ attr ] ) ;
501
- } ) ;
502
- } else {
492
+ // If the user provides a relative path outside of a workspace
493
+ // folder, warn them, but only once.
494
+ let didWarn = false ;
495
+ const makeRelative = ( s : string ) => {
496
+ if ( folder ) {
497
+ return path . join ( folder . uri . fsPath , s ) ;
498
+ }
499
+
500
+ if ( ! didWarn ) {
501
+ didWarn = true ;
503
502
this . showWarning (
504
503
'relativePathsWithoutWorkspaceFolder' ,
505
504
'Behavior when using relative paths without a workspace folder for `cwd`, `program`, or `output` is undefined.'
506
505
) ;
507
506
}
508
- }
507
+
508
+ return s ;
509
+ } ;
510
+
511
+ // 1. Relative paths -> absolute paths
512
+ [ 'cwd' , 'output' , 'program' ] . forEach ( ( attr ) => {
513
+ const value = debugConfiguration [ attr ] ;
514
+ if ( ! value || path . isAbsolute ( value ) ) return ;
515
+
516
+ // Make the path relative (the program attribute needs
517
+ // additional checks).
518
+ if ( attr !== 'program' ) {
519
+ debugConfiguration [ attr ] = makeRelative ( value ) ;
520
+ return ;
521
+ }
522
+
523
+ // If the program could be a package URL, don't alter it unless
524
+ // we can confirm that it is also a file path.
525
+ if ( ! isPackageUrl ( value ) || isFsPath ( value , folder ?. uri . fsPath ) ) {
526
+ debugConfiguration [ attr ] = makeRelative ( value ) ;
527
+ }
528
+ } ) ;
529
+
509
530
// 2. For launch debug/test modes that builds the debug target,
510
531
// delve needs to be launched from the right directory (inside the main module of the target).
511
532
// Compute the launch dir heuristically, and translate the dirname in program to a path relative to buildDir.
@@ -518,7 +539,8 @@ export class GoDebugConfigurationProvider implements vscode.DebugConfigurationPr
518
539
// with a relative path. (https://github.com/golang/vscode-go/issues/1713)
519
540
// parseDebugProgramArgSync will throw an error if `program` is invalid.
520
541
const { program, dirname, programIsDirectory } = parseDebugProgramArgSync (
521
- debugConfiguration [ 'program' ]
542
+ debugConfiguration [ 'program' ] ,
543
+ folder ?. uri . fsPath
522
544
) ;
523
545
if (
524
546
dirname &&
@@ -583,11 +605,15 @@ export async function maybeJoinFlags(dlvToolPath: string, flags: string | string
583
605
584
606
// parseDebugProgramArgSync parses program arg of debug/auto/test launch requests.
585
607
export function parseDebugProgramArgSync (
586
- program : string
587
- ) : { program : string ; dirname : string ; programIsDirectory : boolean } {
608
+ program : string ,
609
+ cwd ?: string
610
+ ) : { program : string ; dirname ?: string ; programIsDirectory : boolean } {
588
611
if ( ! program ) {
589
612
throw new Error ( 'The program attribute is missing in the debug configuration in launch.json' ) ;
590
613
}
614
+ if ( isPackageUrl ( program ) && ! isFsPath ( program , cwd ) ) {
615
+ return { program, programIsDirectory : true } ;
616
+ }
591
617
try {
592
618
const pstats = lstatSync ( program ) ;
593
619
if ( pstats . isDirectory ( ) ) {
@@ -606,3 +632,47 @@ export function parseDebugProgramArgSync(
606
632
`The program attribute '${ program } ' must be a valid directory or .go file in debug/test/auto modes.`
607
633
) ;
608
634
}
635
+
636
+ /**
637
+ * Returns true if the given string is an absolute path or refers to a file or
638
+ * directory in the current working directory, or `wd` if specified.
639
+ * @param s The prospective file or directory path.
640
+ * @param wd The working directory to use instead of `process.cwd()`.
641
+ */
642
+ function isFsPath ( s : string , wd ?: string ) {
643
+ // If it's absolute, it's a path.
644
+ if ( path . isAbsolute ( s ) ) return ;
645
+
646
+ // If the caller specifies a working directory, make the prospective path
647
+ // absolute.
648
+ if ( wd ) s = path . join ( wd , s ) ;
649
+
650
+ try {
651
+ // If lstat doesn't throw, the path refers to a file or directory.
652
+ lstatSync ( s ) ;
653
+ return true ;
654
+ } catch ( error ) {
655
+ // ENOENT means nothing exists at the specified path.
656
+ if ( error instanceof Error && 'code' in error && ( error as NodeJS . ErrnoException ) . code === 'ENOENT' ) {
657
+ return false ;
658
+ }
659
+
660
+ // If the error is something unexpected, rethrow it.
661
+ throw error ;
662
+ }
663
+ }
664
+
665
+ function isPackageUrl ( s : string ) {
666
+ // If the string does not contain `/` and ends with .go it is most likely
667
+ // intended to be a file path. If the file exists it would be caught by
668
+ // isFsPath, but otherwise "the file doesn't exist" is much less confusing
669
+ // than "the package doesn't exist" if the user is trying to execute a test
670
+ // file and got the path wrong.
671
+ if ( s . match ( / ^ [ ^ / ] * \. g o $ / ) ) {
672
+ return s ;
673
+ }
674
+
675
+ // If the string starts with domain.tld/ and it doesn't reference a file,
676
+ // assume it's a package URL
677
+ return s . match ( / ^ [ a - z A - Z 0 - 9 ] [ a - z A - Z 0 - 9 - ] { 0 , 61 } [ a - z A - Z 0 - 9 ] ( \. [ a - z A - Z ] { 2 , } ) + \/ / ) ;
678
+ }
0 commit comments