diff --git a/Nodejs/Product/Nodejs/Nodejs.csproj b/Nodejs/Product/Nodejs/Nodejs.csproj
index 28de1a8d6..1009639fd 100644
--- a/Nodejs/Product/Nodejs/Nodejs.csproj
+++ b/Nodejs/Product/Nodejs/Nodejs.csproj
@@ -312,6 +312,14 @@
PreserveNewest
true
+
+ PreserveNewest
+ true
+
+
+ PreserveNewest
+ true
+
diff --git a/Nodejs/Product/Nodejs/NodejsPackage.cs b/Nodejs/Product/Nodejs/NodejsPackage.cs
index bb90e598e..74480b9cc 100644
--- a/Nodejs/Product/Nodejs/NodejsPackage.cs
+++ b/Nodejs/Product/Nodejs/NodejsPackage.cs
@@ -24,6 +24,36 @@
namespace Microsoft.NodejsTools
{
+ [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
+ internal sealed class ProvideUnifiedSettingsOptionPageAttribute : RegistrationAttribute
+ {
+ private readonly string categoryName;
+ private readonly string pageName;
+
+ public ProvideUnifiedSettingsOptionPageAttribute(string categoryName, string pageName)
+ {
+ this.categoryName = categoryName;
+ this.pageName = pageName;
+ }
+
+ public override void Register(RegistrationContext context)
+ {
+ using (var pageKey = context.CreateKey(
+ string.Format(
+ CultureInfo.InvariantCulture,
+ @"ToolsOptionsPages\{0}\{1}",
+ this.categoryName,
+ this.pageName)))
+ {
+ pageKey.SetValue("IsInUnifiedSettings", 1);
+ }
+ }
+
+ public override void Unregister(RegistrationContext context)
+ {
+ }
+ }
+
///
/// This is the class that implements the package exposed by this assembly.
///
@@ -39,6 +69,7 @@ namespace Microsoft.NodejsTools
[PackageRegistration(UseManagedResourcesOnly = true)]
[Guid(Guids.NodejsPackageString)]
[ProvideOptionPage(typeof(NodejsGeneralOptionsPage), "Node.js Tools", "General", 114, 115, true)]
+ [ProvideUnifiedSettingsOptionPage("Node.js Tools", "General")]
[WebSiteProject("JavaScript", "JavaScript")]
[ProvideProjectFactory(typeof(NodejsProjectFactory), null, null, null, null, ".\\NullPath", LanguageVsTemplate = NodejsConstants.Nodejs, SortPriority = 0x17)] // outer flavor, no file extension
[ProvideMenuResource("Menus.ctmenu", 1)] // This attribute is needed to let the shell know that this package exposes some menus.
diff --git a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs
index 87b957996..63ed05ede 100644
--- a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs
+++ b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs
@@ -14,6 +14,12 @@ public class NodejsGeneralOptionsPage : NodejsDialogPage
private const string CheckForLongPathsSetting = "CheckForLongPaths";
private NodejsGeneralOptionsControl _window;
+ private bool _waitOnAbnormalExit;
+ private bool _waitOnNormalExit;
+ private bool _editAndContinue;
+ private bool _waitOnAbnormalExitModified;
+ private bool _waitOnNormalExitModified;
+ private bool _editAndContinueModified;
public NodejsGeneralOptionsPage()
: base("General")
@@ -38,18 +44,42 @@ protected override IWin32Window Window
/// True if Node processes should pause for input before exiting
/// if they exit abnormally.
///
- public bool WaitOnAbnormalExit { get; set; }
+ public bool WaitOnAbnormalExit
+ {
+ get => this._waitOnAbnormalExit;
+ set
+ {
+ this._waitOnAbnormalExit = value;
+ this._waitOnAbnormalExitModified = true;
+ }
+ }
///
/// True if Node processes should pause for input before exiting
/// if they exit normally.
///
- public bool WaitOnNormalExit { get; set; }
+ public bool WaitOnNormalExit
+ {
+ get => this._waitOnNormalExit;
+ set
+ {
+ this._waitOnNormalExit = value;
+ this._waitOnNormalExitModified = true;
+ }
+ }
///
/// Indicates whether Edit and Continue feature should be enabled.
///
- public bool EditAndContinue { get; set; }
+ public bool EditAndContinue
+ {
+ get => this._editAndContinue;
+ set
+ {
+ this._editAndContinue = value;
+ this._editAndContinueModified = true;
+ }
+ }
///
/// Resets settings back to their defaults. This should be followed by
@@ -65,12 +95,36 @@ public override void ResetSettings()
public override void LoadSettingsFromStorage()
{
- // Load settings from storage.
- this.WaitOnAbnormalExit = LoadBool(WaitOnAbnormalExitSetting) ?? true;
- this.WaitOnNormalExit = LoadBool(WaitOnNormalExitSetting) ?? false;
- this.EditAndContinue = LoadBool(EditAndContinueSetting) ?? true;
+ this._waitOnAbnormalExit = LoadBool(WaitOnAbnormalExitSetting) ?? true;
+ this._waitOnNormalExit = LoadBool(WaitOnNormalExitSetting) ?? false;
+ this._editAndContinue = LoadBool(EditAndContinueSetting) ?? true;
+ this._waitOnAbnormalExitModified = false;
+ this._waitOnNormalExitModified = false;
+ this._editAndContinueModified = false;
+
+ if (this._window != null)
+ {
+ this._window.SyncControlWithPageSettings(this);
+ }
+ }
+
+ internal void RefreshSettingsFromStorage()
+ {
+ if (!this._waitOnAbnormalExitModified)
+ {
+ this._waitOnAbnormalExit = LoadBool(WaitOnAbnormalExitSetting) ?? true;
+ }
+
+ if (!this._waitOnNormalExitModified)
+ {
+ this._waitOnNormalExit = LoadBool(WaitOnNormalExitSetting) ?? false;
+ }
+
+ if (!this._editAndContinueModified)
+ {
+ this._editAndContinue = LoadBool(EditAndContinueSetting) ?? true;
+ }
- // Synchronize UI with backing properties.
if (this._window != null)
{
this._window.SyncControlWithPageSettings(this);
@@ -79,16 +133,17 @@ public override void LoadSettingsFromStorage()
public override void SaveSettingsToStorage()
{
- // Synchronize backing properties with UI.
if (this._window != null)
{
this._window.SyncPageWithControlSettings(this);
}
- // Save settings.
SaveBool(WaitOnNormalExitSetting, this.WaitOnNormalExit);
SaveBool(WaitOnAbnormalExitSetting, this.WaitOnAbnormalExit);
SaveBool(EditAndContinueSetting, this.EditAndContinue);
+ this._waitOnAbnormalExitModified = false;
+ this._waitOnNormalExitModified = false;
+ this._editAndContinueModified = false;
}
}
}
diff --git a/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs b/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs
index fb777c2de..c79375c6d 100644
--- a/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs
+++ b/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs
@@ -136,10 +136,13 @@ private void StartNodeProcess(string file, string nodePath, bool shouldStartBrow
psi.EnvironmentVariables[nameValue.Key] = nameValue.Value;
}
+ var generalOptions = NodejsPackage.Instance.GeneralOptionsPage;
+ generalOptions.RefreshSettingsFromStorage();
+
var process = NodeProcess.Start(
psi,
- waitOnAbnormal: NodejsPackage.Instance.GeneralOptionsPage.WaitOnAbnormalExit,
- waitOnNormal: NodejsPackage.Instance.GeneralOptionsPage.WaitOnNormalExit);
+ waitOnAbnormal: generalOptions.WaitOnAbnormalExit,
+ waitOnNormal: generalOptions.WaitOnNormalExit);
this._project.OnDispose += process.ResponseToTerminateEvent;
diff --git a/Nodejs/Product/Nodejs/UnifiedSettings.pkgdef b/Nodejs/Product/Nodejs/UnifiedSettings.pkgdef
new file mode 100644
index 000000000..a8462442f
--- /dev/null
+++ b/Nodejs/Product/Nodejs/UnifiedSettings.pkgdef
@@ -0,0 +1,5 @@
+// CacheTag must change whenever the Unified Settings manifest changes.
+[$RootKey$\SettingsManifests\{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}]
+@="Microsoft.NodejsTools.NodejsPackage"
+"ManifestPath"="$PackageFolder$\UnifiedSettings\NodejsGeneralOptions.registration.json"
+"CacheTag"=qword:01DD3FA6EA131C30
diff --git a/Nodejs/Product/Nodejs/UnifiedSettings/NodejsGeneralOptions.registration.json b/Nodejs/Product/Nodejs/UnifiedSettings/NodejsGeneralOptions.registration.json
new file mode 100644
index 000000000..4c1c2a03d
--- /dev/null
+++ b/Nodejs/Product/Nodejs/UnifiedSettings/NodejsGeneralOptions.registration.json
@@ -0,0 +1,95 @@
+{
+ "$schema": "https://aka.ms/unified-settings-experience/registration/schema",
+ "properties": {
+ "debugging.nodejs.general.waitOnAbnormalExit": {
+ "type": "boolean",
+ "title": "@UnifiedSettings_WaitOnAbnormalExit;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}",
+ "default": true,
+ "order": 0,
+ "migration": {
+ "custom": {
+ "mode": "full",
+ "inputs": [
+ {
+ "store": "VsUserSettingsRegistry",
+ "path": "NodejsTools\\Options\\General\\WaitOnAbnormalExit"
+ }
+ ],
+ "map": [
+ {
+ "result": true,
+ "matches": [ "True" ]
+ },
+ {
+ "result": false,
+ "matches": [ "False" ]
+ }
+ ]
+ }
+ }
+ },
+ "debugging.nodejs.general.waitOnNormalExit": {
+ "type": "boolean",
+ "title": "@UnifiedSettings_WaitOnNormalExit;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}",
+ "default": false,
+ "order": 1,
+ "migration": {
+ "custom": {
+ "mode": "full",
+ "inputs": [
+ {
+ "store": "VsUserSettingsRegistry",
+ "path": "NodejsTools\\Options\\General\\WaitOnNormalExit"
+ }
+ ],
+ "map": [
+ {
+ "result": true,
+ "matches": [ "True" ]
+ },
+ {
+ "result": false,
+ "matches": [ "False" ]
+ }
+ ]
+ }
+ }
+ },
+ "debugging.nodejs.general.editAndContinue": {
+ "type": "boolean",
+ "title": "@UnifiedSettings_EditAndContinue;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}",
+ "default": true,
+ "order": 2,
+ "migration": {
+ "custom": {
+ "mode": "full",
+ "inputs": [
+ {
+ "store": "VsUserSettingsRegistry",
+ "path": "NodejsTools\\Options\\General\\EditAndContinue"
+ }
+ ],
+ "map": [
+ {
+ "result": true,
+ "matches": [ "True" ]
+ },
+ {
+ "result": false,
+ "matches": [ "False" ]
+ }
+ ]
+ }
+ }
+ }
+ },
+ "categories": {
+ "debugging.nodejs": {
+ "title": "@114;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}"
+ },
+ "debugging.nodejs.general": {
+ "title": "@115;{FE8A8C3D-328A-476D-99F9-2A24B75F8C7F}",
+ "legacyOptionPageId": "EF677A38-0953-39C2-A228-2FBE8F8F082E"
+ }
+ }
+}
diff --git a/Nodejs/Product/Nodejs/VSPackage.resx b/Nodejs/Product/Nodejs/VSPackage.resx
index bb986d846..e66cd35a3 100644
--- a/Nodejs/Product/Nodejs/VSPackage.resx
+++ b/Nodejs/Product/Nodejs/VSPackage.resx
@@ -138,6 +138,15 @@
General
+
+ Wait for input when process exits abnormally
+
+
+ Wait for input when process exits normally
+
+
+ Enable Edit and Continue
+
Npm
diff --git a/Nodejs/Tests/Core/NodejsGeneralOptionsTests.cs b/Nodejs/Tests/Core/NodejsGeneralOptionsTests.cs
new file mode 100644
index 000000000..04057c22c
--- /dev/null
+++ b/Nodejs/Tests/Core/NodejsGeneralOptionsTests.cs
@@ -0,0 +1,366 @@
+// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Reflection;
+using System.Resources;
+using Microsoft.NodejsTools.Options;
+using Microsoft.VisualStudio.Shell;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+using Newtonsoft.Json.Linq;
+
+namespace NodejsTests
+{
+ [TestClass]
+ public class NodejsGeneralOptionsTests
+ {
+ private const string PackageGuid = "FE8A8C3D-328A-476D-99F9-2A24B75F8C7F";
+ private const string LegacyPageGuid = "EF677A38-0953-39C2-A228-2FBE8F8F082E";
+
+ [TestInitialize]
+ public void InitializeThreadHelper()
+ {
+ var contextField = typeof(ThreadHelper).GetField(
+ "_joinableTaskContextCache",
+ BindingFlags.NonPublic | BindingFlags.Static);
+ if (contextField.GetValue(null) == null)
+ {
+ _ = System.Windows.Threading.Dispatcher.CurrentDispatcher;
+ typeof(ThreadHelper)
+ .GetMethod("SetUIThread", BindingFlags.NonPublic | BindingFlags.Static)
+ .Invoke(null, null);
+ contextField.SetValue(null, Activator.CreateInstance(contextField.FieldType));
+ }
+ }
+
+ [TestMethod, Priority(0)]
+ public void GeneralOptionsUseLegacyDefaults()
+ {
+ var page = new TestGeneralOptionsPage();
+
+ page.LoadSettingsFromStorage();
+
+ Assert.IsTrue(page.WaitOnAbnormalExit);
+ Assert.IsFalse(page.WaitOnNormalExit);
+ Assert.IsTrue(page.EditAndContinue);
+ }
+
+ [TestMethod, Priority(0)]
+ public void GeneralOptionsRefreshUnmodifiedValues()
+ {
+ var page = new TestGeneralOptionsPage
+ {
+ StoredValues =
+ {
+ ["WaitOnAbnormalExit"] = false,
+ ["WaitOnNormalExit"] = true,
+ ["EditAndContinue"] = false
+ }
+ };
+ page.LoadSettingsFromStorage();
+
+ page.StoredValues["WaitOnAbnormalExit"] = true;
+ page.StoredValues["WaitOnNormalExit"] = false;
+ page.StoredValues["EditAndContinue"] = true;
+ page.RefreshSettingsFromStorage();
+
+ Assert.IsTrue(page.WaitOnAbnormalExit);
+ Assert.IsFalse(page.WaitOnNormalExit);
+ Assert.IsTrue(page.EditAndContinue);
+ }
+
+ [TestMethod, Priority(0)]
+ public void GeneralOptionsRefreshPreservesUnsavedConsumerValues()
+ {
+ var page = new TestGeneralOptionsPage
+ {
+ StoredValues =
+ {
+ ["WaitOnAbnormalExit"] = true,
+ ["WaitOnNormalExit"] = true,
+ ["EditAndContinue"] = false
+ }
+ };
+ page.LoadSettingsFromStorage();
+
+ page.WaitOnAbnormalExit = false;
+ page.StoredValues["WaitOnNormalExit"] = false;
+ page.StoredValues["EditAndContinue"] = true;
+ page.RefreshSettingsFromStorage();
+
+ Assert.IsFalse(page.WaitOnAbnormalExit);
+ Assert.IsFalse(page.WaitOnNormalExit);
+ Assert.IsTrue(page.EditAndContinue);
+ }
+
+ [TestMethod, Priority(0)]
+ public void GeneralOptionsSaveAllLegacyValues()
+ {
+ var page = new TestGeneralOptionsPage
+ {
+ WaitOnAbnormalExit = false,
+ WaitOnNormalExit = true,
+ EditAndContinue = false
+ };
+
+ page.SaveSettingsToStorage();
+
+ Assert.AreEqual(false, page.SavedValues["WaitOnAbnormalExit"]);
+ Assert.AreEqual(true, page.SavedValues["WaitOnNormalExit"]);
+ Assert.AreEqual(false, page.SavedValues["EditAndContinue"]);
+ }
+
+ [TestMethod, Priority(0)]
+ public void UnifiedSettingsManifestMatchesLegacyContract()
+ {
+ var manifestPath = Path.Combine(
+ AppDomain.CurrentDomain.BaseDirectory,
+ "UnifiedSettings",
+ "NodejsGeneralOptions.registration.json");
+ var manifest = JObject.Parse(File.ReadAllText(manifestPath));
+ var properties = (JObject)manifest["properties"];
+ var categories = (JObject)manifest["categories"];
+
+ Assert.AreEqual(3, properties.Count);
+ AssertSetting(
+ properties,
+ "debugging.nodejs.general.waitOnAbnormalExit",
+ "WaitOnAbnormalExit",
+ true,
+ "UnifiedSettings_WaitOnAbnormalExit");
+ AssertSetting(
+ properties,
+ "debugging.nodejs.general.waitOnNormalExit",
+ "WaitOnNormalExit",
+ false,
+ "UnifiedSettings_WaitOnNormalExit");
+ AssertSetting(
+ properties,
+ "debugging.nodejs.general.editAndContinue",
+ "EditAndContinue",
+ true,
+ "UnifiedSettings_EditAndContinue");
+
+ Assert.AreEqual(2, categories.Count);
+ Assert.AreEqual(
+ string.Format("@114;{{{0}}}", PackageGuid),
+ (string)categories["debugging.nodejs"]["title"]);
+ Assert.AreEqual(
+ string.Format("@115;{{{0}}}", PackageGuid),
+ (string)categories["debugging.nodejs.general"]["title"]);
+ Assert.AreEqual(
+ LegacyPageGuid,
+ (string)categories["debugging.nodejs.general"]["legacyOptionPageId"]);
+ }
+
+ [TestMethod, Priority(0)]
+ public void UnifiedSettingsPackageRegistrationPointsToManifest()
+ {
+ var pkgdef = File.ReadAllText(
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UnifiedSettings.pkgdef"));
+
+ StringAssert.Contains(pkgdef, @"SettingsManifests\{" + PackageGuid + "}");
+ StringAssert.Contains(
+ pkgdef,
+ @"""ManifestPath""=""$PackageFolder$\UnifiedSettings\NodejsGeneralOptions.registration.json""");
+ StringAssert.Contains(pkgdef, @"""CacheTag""=qword:");
+ }
+
+ [TestMethod, Priority(0)]
+ public void UnifiedSettingsHierarchyExcludesLegacyPlaceholder()
+ {
+ Assert.AreEqual(new Guid(LegacyPageGuid), typeof(NodejsGeneralOptionsPage).GUID);
+
+ var generatedPkgdef = File.ReadAllText(
+ Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Microsoft.NodejsTools.pkgdef"));
+ var pageKey = @"[$RootKey$\ToolsOptionsPages\Node.js Tools\General]";
+ var pageRegistration = GetPkgdefRegistration(
+ generatedPkgdef,
+ pageKey).ToUpperInvariant();
+
+ Assert.IsFalse(string.IsNullOrEmpty(pageRegistration));
+ StringAssert.Contains(
+ pageRegistration,
+ string.Format(@"""PAGE""=""{{{0}}}""", LegacyPageGuid));
+ StringAssert.Contains(
+ pageRegistration,
+ @"""ISINUNIFIEDSETTINGS""=DWORD:00000001");
+ var isInUnifiedSettings = pageRegistration.Contains(
+ @"""ISINUNIFIEDSETTINGS""=DWORD:00000001");
+
+ var manifestPath = Path.Combine(
+ AppDomain.CurrentDomain.BaseDirectory,
+ "UnifiedSettings",
+ "NodejsGeneralOptions.registration.json");
+ var categories = (JObject)JObject.Parse(
+ File.ReadAllText(manifestPath))["categories"];
+ var realCategoryCount = 0;
+ foreach (var categoryProperty in categories.Properties())
+ {
+ var category = (JObject)categoryProperty.Value;
+ if (string.Equals(
+ LegacyPageGuid,
+ (string)category["legacyOptionPageId"],
+ StringComparison.OrdinalIgnoreCase))
+ {
+ ++realCategoryCount;
+ }
+ }
+
+ // ToolsOptionsHierarchyMerger excludes legacy leaves carrying this registration value.
+ var legacyPlaceholderCount = isInUnifiedSettings ? 0 : 1;
+ Assert.AreEqual(1, realCategoryCount);
+ Assert.AreEqual(0, legacyPlaceholderCount);
+ }
+
+ private static string GetPkgdefRegistration(string pkgdef, string key)
+ {
+ var registration = string.Empty;
+ var searchStart = 0;
+
+ while (searchStart < pkgdef.Length)
+ {
+ var keyStart = pkgdef.IndexOf(key, searchStart, StringComparison.Ordinal);
+ if (keyStart < 0)
+ {
+ break;
+ }
+
+ var keyEnd = pkgdef.IndexOf(
+ "\n[",
+ keyStart + key.Length,
+ StringComparison.Ordinal);
+ if (keyEnd < 0)
+ {
+ keyEnd = pkgdef.Length;
+ }
+
+ registration += pkgdef.Substring(keyStart, keyEnd - keyStart);
+ searchStart = keyEnd;
+ }
+
+ return registration;
+ }
+
+ [TestMethod, Priority(0)]
+ public void UnifiedSettingsResourcesResolveWithPackageProviderSyntax()
+ {
+ var manifestPath = Path.Combine(
+ AppDomain.CurrentDomain.BaseDirectory,
+ "UnifiedSettings",
+ "NodejsGeneralOptions.registration.json");
+ var manifest = JObject.Parse(File.ReadAllText(manifestPath));
+ var resourceTokens = new List();
+
+ AddDisplayResourceTokens((JObject)manifest["properties"], resourceTokens);
+ AddDisplayResourceTokens((JObject)manifest["categories"], resourceTokens);
+
+ Assert.AreEqual(5, resourceTokens.Count);
+
+ var resourceManager = new ResourceManager(
+ "VSPackage",
+ typeof(NodejsGeneralOptionsPage).Assembly);
+
+ foreach (var token in resourceTokens)
+ {
+ var fallbackSeparator = token.IndexOf('|');
+ var resourceId = fallbackSeparator < 0
+ ? token
+ : token.Substring(0, fallbackSeparator);
+ var providerSeparator = resourceId.IndexOf(';');
+
+ Assert.AreEqual('@', resourceId[0], token);
+ Assert.IsTrue(providerSeparator > 1, token);
+
+ var resourceName = resourceId.Substring(1, providerSeparator - 1);
+ Assert.IsTrue(
+ Guid.TryParse(resourceId.Substring(providerSeparator + 1), out var packageGuid),
+ token);
+ Assert.AreEqual(new Guid(PackageGuid), packageGuid, token);
+
+ var resolved = resourceManager.GetString(
+ resourceName,
+ CultureInfo.InvariantCulture);
+ Assert.IsFalse(string.IsNullOrEmpty(resolved), token);
+ Assert.AreNotEqual(token, resolved, token);
+ }
+ }
+
+ private static void AddDisplayResourceTokens(
+ JObject definitions,
+ ICollection resourceTokens)
+ {
+ foreach (var definitionProperty in definitions.Properties())
+ {
+ var definition = (JObject)definitionProperty.Value;
+ foreach (var fieldName in new[] { "title", "description" })
+ {
+ var token = (string)definition[fieldName];
+ if (token != null)
+ {
+ resourceTokens.Add(token);
+ }
+ }
+ }
+ }
+
+ private static void AssertSetting(
+ JObject properties,
+ string moniker,
+ string legacyName,
+ bool defaultValue,
+ string resourceName)
+ {
+ var property = (JObject)properties[moniker];
+ Assert.IsNotNull(property, moniker);
+ Assert.AreEqual("boolean", (string)property["type"], moniker);
+ Assert.AreEqual(defaultValue, (bool)property["default"], moniker);
+ Assert.AreEqual(
+ string.Format("@{0};{{{1}}}", resourceName, PackageGuid),
+ (string)property["title"],
+ moniker);
+
+ var migration = (JObject)property["migration"]["custom"];
+ Assert.AreEqual("full", (string)migration["mode"], moniker);
+ Assert.AreEqual(1, ((JArray)migration["inputs"]).Count, moniker);
+ Assert.AreEqual(
+ "VsUserSettingsRegistry",
+ (string)migration["inputs"][0]["store"],
+ moniker);
+ Assert.AreEqual(
+ @"NodejsTools\Options\General\" + legacyName,
+ (string)migration["inputs"][0]["path"],
+ moniker);
+
+ var map = (JArray)migration["map"];
+ Assert.AreEqual(2, map.Count, moniker);
+ Assert.AreEqual(true, (bool)map[0]["result"], moniker);
+ Assert.AreEqual("True", (string)map[0]["matches"][0], moniker);
+ Assert.AreEqual(false, (bool)map[1]["result"], moniker);
+ Assert.AreEqual("False", (string)map[1]["matches"][0], moniker);
+ }
+
+ private sealed class TestGeneralOptionsPage : NodejsGeneralOptionsPage
+ {
+ internal IDictionary StoredValues { get; } =
+ new Dictionary();
+
+ internal IDictionary SavedValues { get; } =
+ new Dictionary();
+
+ internal override bool? LoadBool(string name)
+ {
+ return this.StoredValues.TryGetValue(name, out var value)
+ ? value
+ : (bool?)null;
+ }
+
+ internal override void SaveBool(string name, bool value)
+ {
+ this.SavedValues[name] = value;
+ }
+ }
+ }
+}
diff --git a/Nodejs/Tests/Core/NodejsTests.csproj b/Nodejs/Tests/Core/NodejsTests.csproj
index 062cd1c7d..50c65488f 100644
--- a/Nodejs/Tests/Core/NodejsTests.csproj
+++ b/Nodejs/Tests/Core/NodejsTests.csproj
@@ -83,6 +83,7 @@
+
@@ -157,6 +158,16 @@
+
+
+ UnifiedSettings.pkgdef
+ PreserveNewest
+
+
+ UnifiedSettings\NodejsGeneralOptions.registration.json
+ PreserveNewest
+
+
ResXFileCodeGenerator
diff --git a/loc/lcl/CHS/Microsoft.NodejsTools.dll.lcl b/loc/lcl/CHS/Microsoft.NodejsTools.dll.lcl
index 14e89e37f..f1e317fb4 100644
--- a/loc/lcl/CHS/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/CHS/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/CHT/Microsoft.NodejsTools.dll.lcl b/loc/lcl/CHT/Microsoft.NodejsTools.dll.lcl
index cc5b333e5..0e681b680 100644
--- a/loc/lcl/CHT/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/CHT/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/CSY/Microsoft.NodejsTools.dll.lcl b/loc/lcl/CSY/Microsoft.NodejsTools.dll.lcl
index c280bc767..f54f2a286 100644
--- a/loc/lcl/CSY/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/CSY/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/DEU/Microsoft.NodejsTools.dll.lcl b/loc/lcl/DEU/Microsoft.NodejsTools.dll.lcl
index fea5811e8..2faed91e4 100644
--- a/loc/lcl/DEU/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/DEU/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/ESN/Microsoft.NodejsTools.dll.lcl b/loc/lcl/ESN/Microsoft.NodejsTools.dll.lcl
index 019f58b42..acc90588f 100644
--- a/loc/lcl/ESN/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/ESN/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/FRA/Microsoft.NodejsTools.dll.lcl b/loc/lcl/FRA/Microsoft.NodejsTools.dll.lcl
index caaa52bb0..f184063cb 100644
--- a/loc/lcl/FRA/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/FRA/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/ITA/Microsoft.NodejsTools.dll.lcl b/loc/lcl/ITA/Microsoft.NodejsTools.dll.lcl
index 5de3f68c4..353539331 100644
--- a/loc/lcl/ITA/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/ITA/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/JPN/Microsoft.NodejsTools.dll.lcl b/loc/lcl/JPN/Microsoft.NodejsTools.dll.lcl
index 6c38f370a..b666d7e38 100644
--- a/loc/lcl/JPN/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/JPN/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/KOR/Microsoft.NodejsTools.dll.lcl b/loc/lcl/KOR/Microsoft.NodejsTools.dll.lcl
index e9730c6d9..646b4d2b5 100644
--- a/loc/lcl/KOR/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/KOR/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/PLK/Microsoft.NodejsTools.dll.lcl b/loc/lcl/PLK/Microsoft.NodejsTools.dll.lcl
index 6b4ede4f3..93c9e74d5 100644
--- a/loc/lcl/PLK/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/PLK/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/PTB/Microsoft.NodejsTools.dll.lcl b/loc/lcl/PTB/Microsoft.NodejsTools.dll.lcl
index 39a9394f7..5e560d724 100644
--- a/loc/lcl/PTB/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/PTB/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/RUS/Microsoft.NodejsTools.dll.lcl b/loc/lcl/RUS/Microsoft.NodejsTools.dll.lcl
index fe2fdc9bc..0f6e43106 100644
--- a/loc/lcl/RUS/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/RUS/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-
diff --git a/loc/lcl/TRK/Microsoft.NodejsTools.dll.lcl b/loc/lcl/TRK/Microsoft.NodejsTools.dll.lcl
index 23537d10f..e82af7894 100644
--- a/loc/lcl/TRK/Microsoft.NodejsTools.dll.lcl
+++ b/loc/lcl/TRK/Microsoft.NodejsTools.dll.lcl
@@ -4447,6 +4447,33 @@
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
+
-