diff --git a/Extension/package.json b/Extension/package.json index 327895d60c..5f4b580e8c 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -1102,6 +1102,12 @@ "markdownDescription": "%c_cpp.configuration.vcpkg.enabled.markdownDescription%", "scope": "resource" }, + "C_Cpp.addNodeAddonIncludePaths": { + "type": "boolean", + "default": false, + "markdownDescription": "%c_cpp.configuration.addNodeAddonIncludePaths.description%", + "scope": "application" + }, "C_Cpp.renameRequiresIdentifier": { "type": "boolean", "default": true, @@ -2679,4 +2685,4 @@ "integrity": "CF1A01AA75275F76800F6BC1D289F2066DCEBCD983376D344ABF6B03FDB8FEA0" } ] -} \ No newline at end of file +} diff --git a/Extension/package.nls.json b/Extension/package.nls.json index 60486fda1e..6e155b04d3 100644 --- a/Extension/package.nls.json +++ b/Extension/package.nls.json @@ -160,6 +160,7 @@ "c_cpp.configuration.enhancedColorization.description": "If enabled, code is colorized based on IntelliSense. This setting only applies if intelliSenseEngine is set to \"Default\".", "c_cpp.configuration.codeFolding.description": "If enabled, code folding ranges are provided by the language server.", "c_cpp.configuration.vcpkg.enabled.markdownDescription": "Enable integration services for the [vcpkg dependency manager](https://aka.ms/vcpkg/).", + "c_cpp.configuration.addNodeAddonIncludePaths.description": "Add include paths from nan and node-addon-api when they're dependencies.", "c_cpp.configuration.renameRequiresIdentifier.description": "If true, 'Rename Symbol' will require a valid C/C++ identifier.", "c_cpp.configuration.debugger.useBacktickCommandSubstitution.description": "If true, debugger shell command substitution will use obsolete backtick (`).", "c_cpp.contributes.views.cppReferencesView.title": "C/C++: Other references results", diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 9087b726b5..0ef0b69485 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -1367,6 +1367,11 @@ export class DefaultClient implements Client { this.semanticTokensProvider = undefined; } } + // if addNodeAddonIncludePaths was turned on but no includes have been found yet then 1) presume that nan + // or node-addon-api was installed so prompt for reload. + if (changedSettings["addNodeAddonIncludePaths"] && settings.addNodeAddonIncludePaths && this.configuration.nodeAddonIncludesFound() === 0) { + util.promptForReloadWindowDueToSettingsChange(); + } } this.configuration.onDidChangeSettings(); telemetry.logLanguageServerEvent("CppSettingsChange", changedSettings, undefined); diff --git a/Extension/src/LanguageServer/configurations.ts b/Extension/src/LanguageServer/configurations.ts index 28a0b6d836..fe86468acd 100644 --- a/Extension/src/LanguageServer/configurations.ts +++ b/Extension/src/LanguageServer/configurations.ts @@ -130,6 +130,7 @@ export class CppProperties { private defaultWindowsSdkVersion: string | null = null; private vcpkgIncludes: string[] = []; private vcpkgPathReady: boolean = false; + private nodeAddonIncludes: string[] = []; private defaultIntelliSenseMode?: string; private defaultCustomConfigurationVariables?: { [key: string]: string }; private readonly configurationGlobPattern: string = "c_cpp_properties.json"; @@ -155,6 +156,7 @@ export class CppProperties { this.configFolder = path.join(rootPath, ".vscode"); this.diagnosticCollection = vscode.languages.createDiagnosticCollection(rootPath); this.buildVcpkgIncludePath(); + this.readNodeAddonIncludeLocations(rootPath); this.disposables.push(vscode.Disposable.from(this.configurationsChanged, this.selectionChanged, this.compileCommandsChanged)); } @@ -317,6 +319,7 @@ export class CppProperties { } else { configuration.includePath = [defaultFolder]; } + // browse.path is not set by default anymore. When it is not set, the includePath will be used instead. if (isUnset(settings.defaultDefines)) { configuration.defines = (process.platform === 'win32') ? ["_DEBUG", "UNICODE", "_UNICODE"] : []; @@ -386,6 +389,72 @@ export class CppProperties { } } + public nodeAddonIncludesFound(): number { + return this.nodeAddonIncludes.length; + } + + private async readNodeAddonIncludeLocations(rootPath: string): Promise { + let error: Error | undefined; + let pdjFound: boolean = false; + const package_json: any = await fs.promises.readFile(path.join(rootPath, "package.json"), "utf8") + .then(pdj => {pdjFound = true; return JSON.parse(pdj); }) + .catch(e => (error = e)); + + if (!error) { + try { + const pathToNode: string = which.sync("node"); + const nodeAddonMap: { [dependency: string]: string } = { + "nan": `"${pathToNode}" --no-warnings -e "require('nan')"`, + "node-addon-api": `"${pathToNode}" --no-warnings -p "require('node-addon-api').include"` + }; + + for (const dep in nodeAddonMap) { + if (dep in package_json.dependencies) { + const execCmd: string = nodeAddonMap[dep]; + let stdout: string = await util.execChildProcess(execCmd, rootPath); + if (!stdout) { + continue; + } + + // cleanup newlines + if (stdout[stdout.length - 1] === "\n") { + stdout = stdout.slice(0, -1); + } + // node-addon-api returns a quoted string, e.g., '"/home/user/dir/node_modules/node-addon-api"'. + if (stdout[0] === "\"" && stdout[stdout.length - 1] === "\"") { + stdout = stdout.slice(1, -1); + } + + // at this time both node-addon-api and nan return their own directory so this test is not really + // needed. but it does future proof the code. + if (!await util.checkDirectoryExists(stdout)) { + // nan returns a path relative to rootPath causing the previous check to fail because this code + // is executing in vscode's working directory. + stdout = path.join(rootPath, stdout); + if (!await util.checkDirectoryExists(stdout)) { + error = new Error(`${dep} directory ${stdout} doesn't exist`); + stdout = ''; + } + } + if (stdout) { + this.nodeAddonIncludes.push(stdout); + } + } + } + } catch (e) { + error = e; + } + } + if (error) { + if (pdjFound) { + // only log an error if package.json exists. + console.log('readNodeAddonIncludeLocations', error.message); + } + } else { + this.handleConfigurationChange(); + } + } + private getConfigIndexForPlatform(config: any): number | undefined { if (!this.configurationJson) { return undefined; @@ -627,6 +696,12 @@ export class CppProperties { const configuration: Configuration = this.configurationJson.configurations[i]; configuration.includePath = this.updateConfigurationStringArray(configuration.includePath, settings.defaultIncludePath, env); + // in case includePath is reset below + const origIncludePath: string[] | undefined = configuration.includePath; + if (settings.addNodeAddonIncludePaths) { + const includePath: string[] = origIncludePath || []; + configuration.includePath = includePath.concat(this.nodeAddonIncludes.filter(i => includePath.indexOf(i) < 0)); + } configuration.defines = this.updateConfigurationStringArray(configuration.defines, settings.defaultDefines, env); configuration.macFrameworkPath = this.updateConfigurationStringArray(configuration.macFrameworkPath, settings.defaultMacFrameworkPath, env); configuration.windowsSdkVersion = this.updateConfigurationString(configuration.windowsSdkVersion, settings.defaultWindowsSdkVersion, env); @@ -663,8 +738,9 @@ export class CppProperties { if (!configuration.windowsSdkVersion && !!this.defaultWindowsSdkVersion) { configuration.windowsSdkVersion = this.defaultWindowsSdkVersion; } - if (!configuration.includePath && !!this.defaultIncludes) { - configuration.includePath = this.defaultIncludes; + if (!origIncludePath && !!this.defaultIncludes) { + const includePath: string[] = configuration.includePath || []; + configuration.includePath = includePath.concat(this.defaultIncludes); } if (!configuration.macFrameworkPath && !!this.defaultFrameworks) { configuration.macFrameworkPath = this.defaultFrameworks; diff --git a/Extension/src/LanguageServer/settings.ts b/Extension/src/LanguageServer/settings.ts index 5e9e492422..14f02f0bd0 100644 --- a/Extension/src/LanguageServer/settings.ts +++ b/Extension/src/LanguageServer/settings.ts @@ -141,6 +141,7 @@ export class CppSettings extends Settings { public get preferredPathSeparator(): string | undefined { return super.Section.get("preferredPathSeparator"); } public get updateChannel(): string | undefined { return super.Section.get("updateChannel"); } public get vcpkgEnabled(): boolean | undefined { return super.Section.get("vcpkg.enabled"); } + public get addNodeAddonIncludePaths(): boolean | undefined { return super.Section.get("addNodeAddonIncludePaths"); } public get renameRequiresIdentifier(): boolean | undefined { return super.Section.get("renameRequiresIdentifier"); } public get defaultIncludePath(): string[] | undefined { return super.Section.get("default.includePath"); } public get defaultDefines(): string[] | undefined { return super.Section.get("default.defines"); }