@@ -10,8 +10,10 @@ import { debounce } from "lodash-es";
1010import semver from "semver" ;
1111import which from "which" ;
1212
13- // Replace any references to predefined variables in config string.
14- // https://code.visualstudio.com/docs/editor/variables-reference#_predefined-variables
13+ /**
14+ * Replace any references to predefined variables in config string.
15+ * https://code.visualstudio.com/docs/editor/variables-reference#_predefined-variables
16+ */
1517export function handleConfigOption ( input : string ) : string {
1618 if ( input . includes ( "${userHome}" ) ) {
1719 input = input . replaceAll ( "${userHome}" , os . homedir ( ) ) ;
@@ -51,60 +53,62 @@ export function handleConfigOption(input: string): string {
5153
5254/** Resolves the absolute executable path and version of a program like Zig or ZLS. */
5355export function resolveExePathAndVersion (
54- /** `null` means lookup in PATH */
55- exePath : string | null ,
56- /** e.g. `zig` or `zig` */
57- exeName : string ,
58- /** e.g. `zig.path` or `zig.zls.path`. Can be null if `exePath === null` */
59- optionName : string | null ,
56+ /**
57+ * - resolves '~' to the user home directory.
58+ * - resolves VS Code predefined variables.
59+ * - resolves possible executable file extensions on windows like '.exe' or '.cmd'.
60+ */
61+ cmd : string ,
6062 /**
6163 * The command-line argument that is used to query the version of the executable.
6264 * Zig uses `version`. ZLS uses `--version`.
6365 */
6466 versionArg : string ,
6567) : { exe : string ; version : semver . SemVer } | { message : string } {
66- /* `optionName === null` implies `exePath === null` */
67- assert ( optionName !== null || exePath === null ) ;
68+ assert ( cmd . length ) ;
6869
69- let resolvedExePath ;
70- if ( ! exePath ) {
71- resolvedExePath = which . sync ( exeName , { nothrow : true } ) ;
72- } else {
73- // allow passing predefined variables
74- resolvedExePath = handleConfigOption ( exePath ) ;
75-
76- if ( resolvedExePath . startsWith ( "~" ) ) {
77- resolvedExePath = path . join ( os . homedir ( ) , resolvedExePath . substring ( 1 ) ) ;
78- } else if ( ! path . isAbsolute ( resolvedExePath ) ) {
79- resolvedExePath = which . sync ( resolvedExePath , { nothrow : true } ) ;
80- }
81- }
70+ // allow passing predefined variables
71+ cmd = handleConfigOption ( cmd ) ;
8272
83- if ( ! resolvedExePath ) {
84- return {
85- message : ( optionName ? `\`${ optionName } \` ` : "" ) + `Could not find '${ exePath ?? exeName } ' in PATH` ,
86- } ;
73+ if ( cmd . startsWith ( "~" ) ) {
74+ cmd = path . join ( os . homedir ( ) , cmd . substring ( 1 ) ) ;
8775 }
8876
89- if ( ! fs . existsSync ( resolvedExePath ) ) {
90- return {
91- message : ( optionName ? `\`${ optionName } \` ` : "" ) + `${ resolvedExePath } does not exist` ,
92- } ;
77+ const isWindows = os . platform ( ) === "win32" ;
78+ const isAbsolute = path . isAbsolute ( cmd ) ;
79+ const hasPathSeparator = ! ! / \/ / . exec ( cmd ) || ( isWindows && ! ! / \\ / . exec ( cmd ) ) ;
80+ if ( ! isAbsolute && hasPathSeparator ) {
81+ // A value like `./zig` would be looked up relative to the cwd of the VS Code process which makes little sense.
82+ return { message : `'${ cmd } ' is not valid` } ;
9383 }
9484
95- try {
96- fs . accessSync ( resolvedExePath , fs . constants . R_OK | fs . constants . X_OK ) ;
97- } catch {
98- return {
99- message : optionName
100- ? `\`${ optionName } \` ${ resolvedExePath } is not an executable`
101- : `${ resolvedExePath } is not an executable` ,
102- } ;
85+ let exePath = which . sync ( cmd , { nothrow : true } ) ;
86+ if ( ! exePath ) {
87+ if ( ! isAbsolute ) {
88+ return { message : `Could not find '${ cmd } ' in PATH` } ;
89+ }
90+
91+ if ( ! fs . existsSync ( cmd ) ) {
92+ return {
93+ message : `'${ cmd } ' does not exist` ,
94+ } ;
95+ }
96+
97+ try {
98+ fs . accessSync ( cmd , fs . constants . R_OK | fs . constants . X_OK ) ;
99+ } catch {
100+ return {
101+ message : `'${ cmd } ' is not an executable` ,
102+ } ;
103+ }
104+
105+ // this code path should be impossible
106+ exePath = cmd ;
103107 }
104108
105- const version = getVersion ( resolvedExePath , versionArg ) ;
106- if ( ! version ) return { message : `Failed to run '${ resolvedExePath } ${ versionArg } '!` } ;
107- return { exe : resolvedExePath , version : version } ;
109+ const version = getVersion ( exePath , versionArg ) ;
110+ if ( ! version ) return { message : `Failed to run '${ exePath } ${ versionArg } '!` } ;
111+ return { exe : exePath , version : version } ;
108112}
109113
110114export function asyncDebounce < T extends ( ...args : unknown [ ] ) => Promise < Awaited < ReturnType < T > > > > (
0 commit comments