@@ -63,7 +63,7 @@ export interface ConfigurationJson {
6363export interface Configuration {
6464 name : string ;
6565 compilerPathInCppPropertiesJson ?: string | null ;
66- compilerPath ?: string | null ;
66+ compilerPath ?: string ;
6767 compilerPathIsExplicit ?: boolean ;
6868 compilerArgs ?: string [ ] ;
6969 compilerArgsLegacy ?: string [ ] ;
@@ -982,14 +982,13 @@ export class CppProperties {
982982 } else {
983983 // However, if compileCommands are used and compilerPath is explicitly set, it's still necessary to resolve variables in it.
984984 if ( configuration . compilerPath === "${default}" ) {
985- configuration . compilerPath = settings . defaultCompilerPath ;
986- }
987- if ( configuration . compilerPath === null ) {
985+ configuration . compilerPath = settings . defaultCompilerPath ?? undefined ;
988986 configuration . compilerPathIsExplicit = true ;
989- } else if ( configuration . compilerPath !== undefined ) {
987+ }
988+ if ( configuration . compilerPath ) {
990989 configuration . compilerPath = util . resolveVariables ( configuration . compilerPath , env ) ;
991990 configuration . compilerPathIsExplicit = true ;
992- } else {
991+ } else if ( configuration . compilerPathIsExplicit === undefined ) {
993992 configuration . compilerPathIsExplicit = false ;
994993 }
995994 }
@@ -1596,88 +1595,93 @@ export class CppProperties {
15961595 return result ;
15971596 }
15981597
1599- private getErrorsForConfigUI ( configIndex : number ) : ConfigurationErrors {
1600- const errors : ConfigurationErrors = { } ;
1601- if ( ! this . configurationJson ) {
1602- return errors ;
1603- }
1604- const isWindows : boolean = os . platform ( ) === 'win32' ;
1605- const config : Configuration = this . configurationJson . configurations [ configIndex ] ;
1606-
1607- // Check if config name is unique.
1608- errors . name = this . isConfigNameUnique ( config . name ) ;
1609- let resolvedCompilerPath : string | undefined | null ;
1610- // Validate compilerPath
1611- if ( config . compilerPath ) {
1612- resolvedCompilerPath = which . sync ( config . compilerPath , { nothrow : true } ) ;
1613- }
1614-
1598+ /**
1599+ * Get the compilerPath and args from a compilerPath string that has already passed through
1600+ * `this.resolvePath`. If there are errors processing the path, those will also be returned.
1601+ *
1602+ * @resolvedCompilerPath a compilerPath string that has already been resolved.
1603+ */
1604+ public static validateCompilerPath ( resolvedCompilerPath : string , rootUri ?: vscode . Uri ) : util . CompilerPathAndArgs {
16151605 if ( ! resolvedCompilerPath ) {
1616- resolvedCompilerPath = this . resolvePath ( config . compilerPath ) ;
1606+ return { compilerName : '' , allCompilerArgs : [ ] , compilerArgsFromCommandLineInPath : [ ] } ;
16171607 }
1618- const settings : CppSettings = new CppSettings ( this . rootUri ) ;
1619- const compilerPathAndArgs : util . CompilerPathAndArgs = util . extractCompilerPathAndArgs ( ! ! settings . legacyCompilerArgsBehavior , resolvedCompilerPath ) ;
1620- if ( resolvedCompilerPath
1621- // Don't error cl.exe paths because it could be for an older preview build.
1622- && compilerPathAndArgs . compilerName . toLowerCase ( ) !== "cl.exe"
1623- && compilerPathAndArgs . compilerName . toLowerCase ( ) !== "cl" ) {
1624- resolvedCompilerPath = resolvedCompilerPath . trim ( ) ;
1625-
1626- // Error when the compiler's path has spaces without quotes but args are used.
1627- // Except, exclude cl.exe paths because it could be for an older preview build.
1628- const compilerPathNeedsQuotes : boolean =
1629- ( compilerPathAndArgs . compilerArgsFromCommandLineInPath && compilerPathAndArgs . compilerArgsFromCommandLineInPath . length > 0 ) &&
1630- ! resolvedCompilerPath . startsWith ( '"' ) &&
1631- compilerPathAndArgs . compilerPath !== undefined &&
1632- compilerPathAndArgs . compilerPath !== null &&
1633- compilerPathAndArgs . compilerPath . includes ( " " ) ;
1634-
1635- const compilerPathErrors : string [ ] = [ ] ;
1636- if ( compilerPathNeedsQuotes ) {
1637- compilerPathErrors . push ( localize ( "path.with.spaces" , 'Compiler path with spaces and arguments is missing double quotes " around the path.' ) ) ;
1638- }
1608+ resolvedCompilerPath = resolvedCompilerPath . trim ( ) ;
1609+
1610+ const settings = new CppSettings ( rootUri ) ;
1611+ const compilerPathAndArgs = util . extractCompilerPathAndArgs ( ! ! settings . legacyCompilerArgsBehavior , resolvedCompilerPath , undefined , rootUri ?. fsPath ) ;
1612+ const compilerLowerCase : string = compilerPathAndArgs . compilerName . toLowerCase ( ) ;
1613+ const isCl : boolean = compilerLowerCase === "cl" || compilerLowerCase === "cl.exe" ;
1614+ const telemetry : { [ key : string ] : number } = { } ;
16391615
1616+ // Don't error cl.exe paths because it could be for an older preview build.
1617+ if ( ! isCl ) {
16401618 // Get compiler path without arguments before checking if it exists
1641- resolvedCompilerPath = compilerPathAndArgs . compilerPath ?? undefined ;
1642- if ( resolvedCompilerPath ) {
1619+ if ( compilerPathAndArgs . compilerPath ) {
1620+ resolvedCompilerPath = compilerPathAndArgs . compilerPath ;
16431621 let pathExists : boolean = true ;
16441622 const existsWithExeAdded : ( path : string ) => boolean = ( path : string ) => isWindows && ! path . startsWith ( "/" ) && fs . existsSync ( path + ".exe" ) ;
16451623 if ( ! fs . existsSync ( resolvedCompilerPath ) ) {
16461624 if ( existsWithExeAdded ( resolvedCompilerPath ) ) {
16471625 resolvedCompilerPath += ".exe" ;
1648- } else if ( ! this . rootUri ) {
1649- pathExists = false ;
16501626 } else {
1651- // Check again for a relative path.
1652- const relativePath : string = this . rootUri . fsPath + path . sep + resolvedCompilerPath ;
1653- if ( ! fs . existsSync ( relativePath ) ) {
1654- if ( existsWithExeAdded ( resolvedCompilerPath ) ) {
1655- resolvedCompilerPath += ".exe" ;
1627+ const pathLocation = which . sync ( resolvedCompilerPath , { nothrow : true } ) ;
1628+ if ( pathLocation ) {
1629+ resolvedCompilerPath = pathLocation ;
1630+ compilerPathAndArgs . compilerPath = pathLocation ;
1631+ } else if ( rootUri ) {
1632+ // Check again for a relative path.
1633+ const relativePath : string = rootUri . fsPath + path . sep + resolvedCompilerPath ;
1634+ if ( ! fs . existsSync ( relativePath ) ) {
1635+ if ( existsWithExeAdded ( relativePath ) ) {
1636+ resolvedCompilerPath = relativePath + ".exe" ;
1637+ } else {
1638+ pathExists = false ;
1639+ }
16561640 } else {
1657- pathExists = false ;
1641+ resolvedCompilerPath = relativePath ;
16581642 }
1659- } else {
1660- resolvedCompilerPath = relativePath ;
16611643 }
16621644 }
16631645 }
16641646
1647+ const compilerPathErrors : string [ ] = [ ] ;
1648+
16651649 if ( ! pathExists ) {
16661650 const message : string = localize ( 'cannot.find' , "Cannot find: {0}" , resolvedCompilerPath ) ;
16671651 compilerPathErrors . push ( message ) ;
1668- } else if ( compilerPathAndArgs . compilerPath === "" ) {
1669- const message : string = localize ( "cannot.resolve.compiler.path" , "Invalid input, cannot resolve compiler path" ) ;
1670- compilerPathErrors . push ( message ) ;
1652+ telemetry . PathNonExistent = 1 ;
16711653 } else if ( ! util . checkExecutableWithoutExtensionExistsSync ( resolvedCompilerPath ) ) {
16721654 const message : string = localize ( "path.is.not.a.file" , "Path is not a file: {0}" , resolvedCompilerPath ) ;
16731655 compilerPathErrors . push ( message ) ;
1656+ telemetry . PathNotAFile = 1 ;
16741657 }
16751658
16761659 if ( compilerPathErrors . length > 0 ) {
1677- errors . compilerPath = compilerPathErrors . join ( '\n' ) ;
1660+ compilerPathAndArgs . error = compilerPathErrors . join ( '\n' ) ;
16781661 }
16791662 }
16801663 }
1664+ compilerPathAndArgs . telemetry = telemetry ;
1665+ return compilerPathAndArgs ;
1666+ }
1667+
1668+ private getErrorsForConfigUI ( configIndex : number ) : ConfigurationErrors {
1669+ const errors : ConfigurationErrors = { } ;
1670+ if ( ! this . configurationJson ) {
1671+ return errors ;
1672+ }
1673+ const isWindows : boolean = os . platform ( ) === 'win32' ;
1674+ const config : Configuration = this . configurationJson . configurations [ configIndex ] ;
1675+
1676+ // Check if config name is unique.
1677+ errors . name = this . isConfigNameUnique ( config . name ) ;
1678+ let resolvedCompilerPath : string | undefined | null ;
1679+ // Validate compilerPath
1680+ if ( ! resolvedCompilerPath ) {
1681+ resolvedCompilerPath = this . resolvePath ( config . compilerPath , false , false ) ;
1682+ }
1683+ const compilerPathAndArgs : util . CompilerPathAndArgs = CppProperties . validateCompilerPath ( resolvedCompilerPath , this . rootUri ) ;
1684+ errors . compilerPath = compilerPathAndArgs . error ;
16811685
16821686 // Validate paths (directories)
16831687 errors . includePath = this . validatePath ( config . includePath , { globPaths : true } ) ;
@@ -1928,7 +1932,6 @@ export class CppProperties {
19281932
19291933 // Check for path-related squiggles.
19301934 const paths : string [ ] = [ ] ;
1931- let compilerPath : string | undefined ;
19321935 for ( const pathArray of [ currentConfiguration . browse ? currentConfiguration . browse . path : undefined , currentConfiguration . includePath , currentConfiguration . macFrameworkPath ] ) {
19331936 if ( pathArray ) {
19341937 for ( const curPath of pathArray ) {
@@ -1950,13 +1953,6 @@ export class CppProperties {
19501953 paths . push ( `${ file } ` ) ;
19511954 } ) ;
19521955
1953- if ( currentConfiguration . compilerPath ) {
1954- // Unlike other cases, compilerPath may not start or end with " due to trimming of whitespace and the possibility of compiler args.
1955- compilerPath = currentConfiguration . compilerPath ;
1956- }
1957-
1958- compilerPath = this . resolvePath ( compilerPath ) . trim ( ) ;
1959-
19601956 // Get the start/end for properties that are file-only.
19611957 const forcedIncludeStart : number = curText . search ( / \s * \" f o r c e d I n c l u d e \" \s * : \s * \[ / ) ;
19621958 const forcedeIncludeEnd : number = forcedIncludeStart === - 1 ? - 1 : curText . indexOf ( "]" , forcedIncludeStart ) ;
@@ -1973,46 +1969,20 @@ export class CppProperties {
19731969 const processedPaths : Set < string > = new Set < string > ( ) ;
19741970
19751971 // Validate compiler paths
1976- let compilerPathNeedsQuotes : boolean = false ;
1977- let compilerMessage : string | undefined ;
1978- const compilerPathAndArgs : util . CompilerPathAndArgs = util . extractCompilerPathAndArgs ( ! ! settings . legacyCompilerArgsBehavior , compilerPath ) ;
1979- const compilerLowerCase : string = compilerPathAndArgs . compilerName . toLowerCase ( ) ;
1980- const isClCompiler : boolean = compilerLowerCase === "cl" || compilerLowerCase === "cl.exe" ;
1981- // Don't squiggle for invalid cl and cl.exe paths.
1982- if ( compilerPathAndArgs . compilerPath && ! isClCompiler ) {
1983- // Squiggle when the compiler's path has spaces without quotes but args are used.
1984- compilerPathNeedsQuotes = ( compilerPathAndArgs . compilerArgsFromCommandLineInPath && compilerPathAndArgs . compilerArgsFromCommandLineInPath . length > 0 )
1985- && ! compilerPath . startsWith ( '"' )
1986- && compilerPathAndArgs . compilerPath . includes ( " " ) ;
1987- compilerPath = compilerPathAndArgs . compilerPath ;
1988- // Don't squiggle if compiler path is resolving with environment path.
1989- if ( compilerPathNeedsQuotes || ( compilerPath && ! which . sync ( compilerPath , { nothrow : true } ) ) ) {
1990- if ( compilerPathNeedsQuotes ) {
1991- compilerMessage = localize ( "path.with.spaces" , 'Compiler path with spaces and arguments is missing double quotes " around the path.' ) ;
1992- newSquiggleMetrics . CompilerPathMissingQuotes ++ ;
1993- } else if ( ! util . checkExecutableWithoutExtensionExistsSync ( compilerPath ) ) {
1994- compilerMessage = localize ( "path.is.not.a.file" , "Path is not a file: {0}" , compilerPath ) ;
1995- newSquiggleMetrics . PathNotAFile ++ ;
1996- }
1997- }
1998- }
1999- let compilerPathExists : boolean = true ;
2000- if ( this . rootUri && ! isClCompiler ) {
2001- const checkPathExists : any = util . checkPathExistsSync ( compilerPath , this . rootUri . fsPath + path . sep , isWindows , true ) ;
2002- compilerPathExists = checkPathExists . pathExists ;
2003- compilerPath = checkPathExists . path ;
2004- }
2005- if ( ! compilerPathExists ) {
2006- compilerMessage = localize ( 'cannot.find' , "Cannot find: {0}" , compilerPath ) ;
2007- newSquiggleMetrics . PathNonExistent ++ ;
2008- }
2009- if ( compilerMessage ) {
1972+ const resolvedCompilerPath = this . resolvePath ( currentConfiguration . compilerPath , false , false ) ;
1973+ const compilerPathAndArgs : util . CompilerPathAndArgs = CppProperties . validateCompilerPath ( resolvedCompilerPath , this . rootUri ) ;
1974+ if ( compilerPathAndArgs . error ) {
20101975 const diagnostic : vscode . Diagnostic = new vscode . Diagnostic (
2011- new vscode . Range ( document . positionAt ( curTextStartOffset + compilerPathValueStart ) ,
2012- document . positionAt ( curTextStartOffset + compilerPathEnd ) ) ,
2013- compilerMessage , vscode . DiagnosticSeverity . Warning ) ;
1976+ new vscode . Range ( document . positionAt ( curTextStartOffset + compilerPathValueStart ) , document . positionAt ( curTextStartOffset + compilerPathEnd ) ) ,
1977+ compilerPathAndArgs . error ,
1978+ vscode . DiagnosticSeverity . Warning ) ;
20141979 diagnostics . push ( diagnostic ) ;
20151980 }
1981+ if ( compilerPathAndArgs . telemetry ) {
1982+ for ( const o of Object . keys ( compilerPathAndArgs . telemetry ) ) {
1983+ newSquiggleMetrics [ o ] = compilerPathAndArgs . telemetry [ o ] ;
1984+ }
1985+ }
20161986
20171987 // validate .config path
20181988 let dotConfigPath : string | undefined ;
0 commit comments