Skip to content

Commit 8cbc066

Browse files
committed
make the compilerPath validation common across JSON and UI
1 parent 799d09f commit 8cbc066

File tree

10 files changed

+329
-144
lines changed

10 files changed

+329
-144
lines changed

Extension/src/LanguageServer/configurations.ts

Lines changed: 75 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export interface ConfigurationJson {
6363
export 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*\"forcedInclude\"\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;

Extension/src/LanguageServer/cppBuildTaskProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export class CppBuildTaskProvider implements TaskProvider {
9898

9999
// Get user compiler path.
100100
const userCompilerPathAndArgs: util.CompilerPathAndArgs | undefined = await activeClient.getCurrentCompilerPathAndArgs();
101-
let userCompilerPath: string | undefined | null;
101+
let userCompilerPath: string | undefined;
102102
if (userCompilerPathAndArgs) {
103103
userCompilerPath = userCompilerPathAndArgs.compilerPath;
104104
if (userCompilerPath && userCompilerPathAndArgs.compilerName) {

0 commit comments

Comments
 (0)