diff --git a/.gitattributes b/.gitattributes index 965f21ef0..613f8f6e5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,5 @@ # Do not normalize line endings from CR LF to LF, regardless of core.autocrlf. -* -text \ No newline at end of file +* -text + +# Normalize C# source files to use CR LF line endings. +*.cs text eol=crlf diff --git a/Nodejs/Common/Telemetry/TelemetryEvents.cs b/Nodejs/Common/Telemetry/TelemetryEvents.cs index 110470a82..1558d52c4 100644 --- a/Nodejs/Common/Telemetry/TelemetryEvents.cs +++ b/Nodejs/Common/Telemetry/TelemetryEvents.cs @@ -1,24 +1,24 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Telemetry { - /// - /// Telemetry event names - /// - internal static class TelemetryEvents { - public const string ProjectImported = "ProjectImported"; - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Telemetry { + /// + /// Telemetry event names + /// + internal static class TelemetryEvents { + public const string ProjectImported = "ProjectImported"; + } +} diff --git a/Nodejs/Common/Telemetry/TelemetryProperties.cs b/Nodejs/Common/Telemetry/TelemetryProperties.cs index eefbd0cb3..596b4d458 100644 --- a/Nodejs/Common/Telemetry/TelemetryProperties.cs +++ b/Nodejs/Common/Telemetry/TelemetryProperties.cs @@ -1,24 +1,24 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Telemetry { - /// - /// Property names for telemetry events - /// - internal static class TelemetryProperties { - public const string ProjectGuid = "ProjectGuid"; - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Telemetry { + /// + /// Property names for telemetry events + /// + internal static class TelemetryProperties { + public const string ProjectGuid = "ProjectGuid"; + } +} diff --git a/Nodejs/NodejsTools.sln.DotSettings b/Nodejs/NodejsTools.sln.DotSettings index 1beb69bcc..43c97791e 100644 --- a/Nodejs/NodejsTools.sln.DotSettings +++ b/Nodejs/NodejsTools.sln.DotSettings @@ -17,4 +17,6 @@ 250 ZeroIndent ZeroIndent - True \ No newline at end of file + True + True + True \ No newline at end of file diff --git a/Nodejs/Product/Analysis/Analysis/AnalysisLimits.cs b/Nodejs/Product/Analysis/Analysis/AnalysisLimits.cs index 601c6055c..f2207f1d1 100644 --- a/Nodejs/Product/Analysis/Analysis/AnalysisLimits.cs +++ b/Nodejs/Product/Analysis/Analysis/AnalysisLimits.cs @@ -148,8 +148,8 @@ public static AnalysisLimits MakeLowAnalysisLimits() { /// /// Gets the maximum number of types which can be merged at once. /// - public int MaxMergeTypes { get; set; } - + public int MaxMergeTypes { get; set; } + /// /// Gets the maximum number of events which can be emitted at once. /// @@ -165,7 +165,7 @@ public static AnalysisLimits MakeLowAnalysisLimits() { /// /// Depth of module file which has to be checked for depth limit. /// True if path too deep in nesting tree; false overwise. - public bool IsPathExceedNestingLimit(int nestedModulesDepth) { + public bool IsPathExceedNestingLimit(int nestedModulesDepth) { return nestedModulesDepth > NestedModulesLimit; } diff --git a/Nodejs/Product/Analysis/Analysis/Analyzer/AnalysisUnit.cs b/Nodejs/Product/Analysis/Analysis/Analyzer/AnalysisUnit.cs index e5c1a99a8..3172c84c5 100644 --- a/Nodejs/Product/Analysis/Analysis/Analyzer/AnalysisUnit.cs +++ b/Nodejs/Product/Analysis/Analysis/Analyzer/AnalysisUnit.cs @@ -183,7 +183,7 @@ internal void Analyze(DDG ddg, CancellationToken cancel) { long endTime = _sw.ElapsedMilliseconds; var thisTime = endTime - startTime; _analysisTime += thisTime; - if (thisTime >= 500 || (_analysisTime / _analysisCount) > 500) { + if (thisTime >= 500 || (_analysisTime / _analysisCount) > 500) { Trace.TraceWarning("Analyzed: {0} {1} ({2} count, {3}ms total, {4}ms mean)", this, thisTime, _analysisCount, _analysisTime, (double)_analysisTime / _analysisCount); } } diff --git a/Nodejs/Product/Analysis/Analysis/Analyzer/RequireAnalysisUnit.cs b/Nodejs/Product/Analysis/Analysis/Analyzer/RequireAnalysisUnit.cs index dd3b2899d..a73d08999 100644 --- a/Nodejs/Product/Analysis/Analysis/Analyzer/RequireAnalysisUnit.cs +++ b/Nodejs/Product/Analysis/Analysis/Analyzer/RequireAnalysisUnit.cs @@ -1,44 +1,44 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Diagnostics; -using System.Threading; - -namespace Microsoft.NodejsTools.Analysis.Analyzer { - class RequireAnalysisUnit : AnalysisUnit { - private string _dependency; - private ModuleTree _tree; - private ModuleTable _table; - - internal RequireAnalysisUnit(ModuleTree tree, ModuleTable table, ProjectEntry entry, string dependency) : base (entry.Tree, entry.EnvironmentRecord) { - _tree = tree; - _table = table; - _dependency = dependency; - } - - internal override void AnalyzeWorker(DDG ddg, CancellationToken cancel) { - ModuleTree module = _table.RequireModule(this, _dependency, _tree); - if (module == null) { - return; - } - - AddChildVisibilitiesExcludingNodeModules(module); - } - - private void AddChildVisibilitiesExcludingNodeModules(ModuleTree moduleTree) { +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Diagnostics; +using System.Threading; + +namespace Microsoft.NodejsTools.Analysis.Analyzer { + class RequireAnalysisUnit : AnalysisUnit { + private string _dependency; + private ModuleTree _tree; + private ModuleTable _table; + + internal RequireAnalysisUnit(ModuleTree tree, ModuleTable table, ProjectEntry entry, string dependency) : base (entry.Tree, entry.EnvironmentRecord) { + _tree = tree; + _table = table; + _dependency = dependency; + } + + internal override void AnalyzeWorker(DDG ddg, CancellationToken cancel) { + ModuleTree module = _table.RequireModule(this, _dependency, _tree); + if (module == null) { + return; + } + + AddChildVisibilitiesExcludingNodeModules(module); + } + + private void AddChildVisibilitiesExcludingNodeModules(ModuleTree moduleTree) { foreach (var childTree in moduleTree.GetChildrenExcludingNodeModules()) { Debug.Assert(childTree.Name != AnalysisConstants.NodeModulesFolder); if (childTree.ProjectEntry == null) { @@ -46,7 +46,7 @@ private void AddChildVisibilitiesExcludingNodeModules(ModuleTree moduleTree) { } else { _table.AddVisibility(_tree, childTree.ProjectEntry); } - } - } - } + } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Analysis/Analysis/GlobalBuilder.cs b/Nodejs/Product/Analysis/Analysis/GlobalBuilder.cs index daef8f3a7..79ecf5b86 100644 --- a/Nodejs/Product/Analysis/Analysis/GlobalBuilder.cs +++ b/Nodejs/Product/Analysis/Analysis/GlobalBuilder.cs @@ -1342,9 +1342,9 @@ private static IAnalysisSet DefineProperties(FunctionValue func, Node node, Anal } private static IAnalysisSet Require(FunctionValue func, Node node, AnalysisUnit unit, IAnalysisSet @this, IAnalysisSet[] args) { - IAnalysisSet res = AnalysisSet.Empty; - - if (node.GetType() != typeof(CallNode)) { + IAnalysisSet res = AnalysisSet.Empty; + + if (node.GetType() != typeof(CallNode)) { return res; } diff --git a/Nodejs/Product/Analysis/Analysis/JsAnalyzer.cs b/Nodejs/Product/Analysis/Analysis/JsAnalyzer.cs index f8a2eecef..a0314ce83 100644 --- a/Nodejs/Product/Analysis/Analysis/JsAnalyzer.cs +++ b/Nodejs/Product/Analysis/Analysis/JsAnalyzer.cs @@ -141,8 +141,8 @@ public IAnalyzable AddPackageJson(string filePath, string entryPoint, List(); - requireAnalysisUnits.AddRange(_dependencies.Select( - dependency => { - return new RequireAnalysisUnit(_tree, _modules, _projectEntry, dependency); - })); - - foreach (var unit in requireAnalysisUnits) { - unit.AnalyzeWorker(null, cancel); - } - } + if (_dependencies != null) { + var requireAnalysisUnits = new List(); + requireAnalysisUnits.AddRange(_dependencies.Select( + dependency => { + return new RequireAnalysisUnit(_tree, _modules, _projectEntry, dependency); + })); + + foreach (var unit in requireAnalysisUnits) { + unit.AnalyzeWorker(null, cancel); + } + } } } diff --git a/Nodejs/Product/Analysis/Analysis/ModuleTreeExtensions.cs b/Nodejs/Product/Analysis/Analysis/ModuleTreeExtensions.cs index 5c118ab62..364c0be37 100644 --- a/Nodejs/Product/Analysis/Analysis/ModuleTreeExtensions.cs +++ b/Nodejs/Product/Analysis/Analysis/ModuleTreeExtensions.cs @@ -1,34 +1,34 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.NodejsTools.Analysis { - internal static class ModuleTreeExtensions { - internal static IEnumerable GetChildrenExcludingNodeModules(this ModuleTree moduleTree) { - if (moduleTree == null) { - return Enumerable.Empty(); - } - // Children.Values returns an IEnumerable - // The process of resolving modules can lead us to add entries into the underlying array - // doing so results in exceptions b/c the array has changed under the enumerable - // To avoid this, we call .ToArray() to create a copy of the array locally which we then Enumerate - return moduleTree.Children.Values.ToArray().Where(mod => mod != null && !String.Equals(mod.Name, AnalysisConstants.NodeModulesFolder, StringComparison.OrdinalIgnoreCase)); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.NodejsTools.Analysis { + internal static class ModuleTreeExtensions { + internal static IEnumerable GetChildrenExcludingNodeModules(this ModuleTree moduleTree) { + if (moduleTree == null) { + return Enumerable.Empty(); + } + // Children.Values returns an IEnumerable + // The process of resolving modules can lead us to add entries into the underlying array + // doing so results in exceptions b/c the array has changed under the enumerable + // To avoid this, we call .ToArray() to create a copy of the array locally which we then Enumerate + return moduleTree.Children.Values.ToArray().Where(mod => mod != null && !String.Equals(mod.Name, AnalysisConstants.NodeModulesFolder, StringComparison.OrdinalIgnoreCase)); + } + } +} diff --git a/Nodejs/Product/AssemblyVersion.cs b/Nodejs/Product/AssemblyVersion.cs index 47e5ac067..03e5f188c 100644 --- a/Nodejs/Product/AssemblyVersion.cs +++ b/Nodejs/Product/AssemblyVersion.cs @@ -1,65 +1,65 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// If you get compiler errors CS0579, "Duplicate '' attribute", check your -// Properties\AssemblyInfo.cs file and remove any lines duplicating the ones below. -// (See also AssemblyInfoCommon.cs in this same directory.) - -#if !SUPPRESS_COMMON_ASSEMBLY_VERSION -[assembly: AssemblyVersion(AssemblyVersionInfo.StableVersion)] -#endif -[assembly: AssemblyFileVersion(AssemblyVersionInfo.Version)] - -class AssemblyVersionInfo { - - // This version string (and the comment for StableVersion) should be - // updated manually between major releases (e.g. from 1.0 to 2.0). - // Servicing branches and minor releases should retain the value. - public const string ReleaseVersion = "1.0"; - - // This version string (and the comment for Version) should be updated - // manually between minor releases (e.g. from 1.0 to 1.1). - // Servicing branches and prereleases should retain the value. - public const string FileVersion = "1.1"; - - // This version should never change from "4100.00"; BuildRelease.ps1 - // will replace it with a generated value. - public const string BuildNumber = "4100.00"; -#if DEV11 - public const string VSMajorVersion = "11"; - const string VSVersionSuffix = "2012"; -#elif DEV12 - public const string VSMajorVersion = "12"; - const string VSVersionSuffix = "2013"; -#elif DEV14 - public const string VSMajorVersion = "14"; - const string VSVersionSuffix = "2015"; -#else -#error Unrecognized VS Version. -#endif - - public const string VSVersion = VSMajorVersion + ".0"; - - // Defaults to "1.0.0.(2012|2013|2015)" - public const string StableVersion = ReleaseVersion + "." + VSVersionSuffix; - - // Defaults to "1.1.4100.00" - public const string Version = FileVersion + "." + BuildNumber; -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// If you get compiler errors CS0579, "Duplicate '' attribute", check your +// Properties\AssemblyInfo.cs file and remove any lines duplicating the ones below. +// (See also AssemblyInfoCommon.cs in this same directory.) + +#if !SUPPRESS_COMMON_ASSEMBLY_VERSION +[assembly: AssemblyVersion(AssemblyVersionInfo.StableVersion)] +#endif +[assembly: AssemblyFileVersion(AssemblyVersionInfo.Version)] + +class AssemblyVersionInfo { + + // This version string (and the comment for StableVersion) should be + // updated manually between major releases (e.g. from 1.0 to 2.0). + // Servicing branches and minor releases should retain the value. + public const string ReleaseVersion = "1.0"; + + // This version string (and the comment for Version) should be updated + // manually between minor releases (e.g. from 1.0 to 1.1). + // Servicing branches and prereleases should retain the value. + public const string FileVersion = "1.1"; + + // This version should never change from "4100.00"; BuildRelease.ps1 + // will replace it with a generated value. + public const string BuildNumber = "4100.00"; +#if DEV11 + public const string VSMajorVersion = "11"; + const string VSVersionSuffix = "2012"; +#elif DEV12 + public const string VSMajorVersion = "12"; + const string VSVersionSuffix = "2013"; +#elif DEV14 + public const string VSMajorVersion = "14"; + const string VSVersionSuffix = "2015"; +#else +#error Unrecognized VS Version. +#endif + + public const string VSVersion = VSMajorVersion + ".0"; + + // Defaults to "1.0.0.(2012|2013|2015)" + public const string StableVersion = ReleaseVersion + "." + VSVersionSuffix; + + // Defaults to "1.1.4100.00" + public const string Version = FileVersion + "." + BuildNumber; +} diff --git a/Nodejs/Product/Nodejs/Azure/WebSiteServiceShims.cs b/Nodejs/Product/Nodejs/Azure/WebSiteServiceShims.cs index 281fbe79c..ef8b39b82 100644 --- a/Nodejs/Product/Nodejs/Azure/WebSiteServiceShims.cs +++ b/Nodejs/Product/Nodejs/Azure/WebSiteServiceShims.cs @@ -57,7 +57,7 @@ public static bool CanShim(object impl) { private object InvokeByName(string name, object[] args) { Type[] types = args != null ? - args.Select(arg => { return arg.GetType(); }).ToArray() : + args.Select(arg => { return arg.GetType(); }).ToArray() : new Type[] { }; var method = new[] { _interface } diff --git a/Nodejs/Product/Nodejs/Commands/DiagnosticsForm.cs b/Nodejs/Product/Nodejs/Commands/DiagnosticsForm.cs index 1f8d44ee8..dc7e0259f 100644 --- a/Nodejs/Product/Nodejs/Commands/DiagnosticsForm.cs +++ b/Nodejs/Product/Nodejs/Commands/DiagnosticsForm.cs @@ -37,10 +37,10 @@ private void _ok_Click(object sender, EventArgs e) { private void _copy_Click(object sender, EventArgs e) { _textBox.SelectAll(); Clipboard.SetText(_textBox.SelectedText); - } - - private void _diagnosticLoggingCheckbox_CheckedChanged(object sender, EventArgs e) { - NodejsPackage.Instance.DiagnosticsOptionsPage.IsLiveDiagnosticsEnabled = _diagnosticLoggingCheckbox.Checked; - } + } + + private void _diagnosticLoggingCheckbox_CheckedChanged(object sender, EventArgs e) { + NodejsPackage.Instance.DiagnosticsOptionsPage.IsLiveDiagnosticsEnabled = _diagnosticLoggingCheckbox.Checked; + } } } diff --git a/Nodejs/Product/Nodejs/Commands/ImportWizardCommand.cs b/Nodejs/Product/Nodejs/Commands/ImportWizardCommand.cs index f94f82931..2d5fe4e16 100644 --- a/Nodejs/Product/Nodejs/Commands/ImportWizardCommand.cs +++ b/Nodejs/Product/Nodejs/Commands/ImportWizardCommand.cs @@ -16,14 +16,14 @@ using System; using System.IO; -using System.Threading.Tasks; +using System.Threading.Tasks; using System.Threading; using System.Windows; using Microsoft.NodejsTools.Project; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudioTools; - + namespace Microsoft.NodejsTools.Commands { /// /// Provides the command to import a project from existing code. @@ -91,9 +91,9 @@ public override void DoCommand(object sender, EventArgs args) { } else { statusBar.SetText("An error occurred and your project was not created."); } - }, - CancellationToken.None, - TaskContinuationOptions.HideScheduler, + }, + CancellationToken.None, + TaskContinuationOptions.HideScheduler, System.Threading.Tasks.TaskScheduler.FromCurrentSynchronizationContext()); } else { statusBar.SetText(""); diff --git a/Nodejs/Product/Nodejs/Debugger/Communication/DebuggerConnection.cs b/Nodejs/Product/Nodejs/Debugger/Communication/DebuggerConnection.cs index d4968c683..aee5b4301 100644 --- a/Nodejs/Product/Nodejs/Debugger/Communication/DebuggerConnection.cs +++ b/Nodejs/Product/Nodejs/Debugger/Communication/DebuggerConnection.cs @@ -36,7 +36,7 @@ sealed class DebuggerConnection : IDebuggerConnection { private readonly INetworkClientFactory _networkClientFactory; private INetworkClient _networkClient; private readonly object _networkClientLock = new object(); - private volatile Version _nodeVersion; + private volatile Version _nodeVersion; private bool _isClosed = false; public DebuggerConnection(INetworkClientFactory networkClientFactory) { @@ -52,10 +52,10 @@ public void Dispose() { /// /// Close connection. /// - public void Close() { + public void Close() { _isClosed = true; - lock (_networkClientLock) { + lock (_networkClientLock) { if (_networkClient != null) { _networkClient.Dispose(); _networkClient = null; @@ -122,42 +122,42 @@ public void Connect(Uri uri) { lock (_networkClientLock) { int connection_attempts = 0; const int MAX_ATTEMPTS = 5; - while (true) { - connection_attempts++; - try { - // TODO: This currently results in a call to the synchronous TcpClient - // constructor, which is a blocking call, and can take a couple of seconds - // to connect (with timeouts and retries). This code is running on the UI - // thread. Ideally this should be connecting async, or moved off the UI thread. - _networkClient = _networkClientFactory.CreateNetworkClient(uri); - - // Unclear if the above can succeed and not be connected, but check for safety. - // The code needs to either break out the while loop, or hit the retry logic - // in the exception handler. - if (_networkClient.Connected) { - LiveLogger.WriteLine("Debugger connected successfully"); - break; - } - else { - throw new SocketException(); - } - } - catch (Exception ex) { - if (ex.IsCriticalException()) { - throw; - } - LiveLogger.WriteLine("Connection attempt {0} failed with: {1}", connection_attempts, ex); - if (_isClosed || connection_attempts >= MAX_ATTEMPTS) { - throw; - } - else { - // See above TODO. This should be moved off the UI thread or posted to retry - // without blocking in the meantime. For now, this seems the lesser of two - // evils. (The other being the debugger failing to attach on launch if the - // debuggee socket wasn't open quickly enough). - System.Threading.Thread.Sleep(200); - } - } + while (true) { + connection_attempts++; + try { + // TODO: This currently results in a call to the synchronous TcpClient + // constructor, which is a blocking call, and can take a couple of seconds + // to connect (with timeouts and retries). This code is running on the UI + // thread. Ideally this should be connecting async, or moved off the UI thread. + _networkClient = _networkClientFactory.CreateNetworkClient(uri); + + // Unclear if the above can succeed and not be connected, but check for safety. + // The code needs to either break out the while loop, or hit the retry logic + // in the exception handler. + if (_networkClient.Connected) { + LiveLogger.WriteLine("Debugger connected successfully"); + break; + } + else { + throw new SocketException(); + } + } + catch (Exception ex) { + if (ex.IsCriticalException()) { + throw; + } + LiveLogger.WriteLine("Connection attempt {0} failed with: {1}", connection_attempts, ex); + if (_isClosed || connection_attempts >= MAX_ATTEMPTS) { + throw; + } + else { + // See above TODO. This should be moved off the UI thread or posted to retry + // without blocking in the meantime. For now, this seems the lesser of two + // evils. (The other being the debugger failing to attach on launch if the + // debuggee socket wasn't open quickly enough). + System.Threading.Thread.Sleep(200); + } + } } } diff --git a/Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7Engine.cs b/Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7Engine.cs index e50f70adb..d56fc4325 100644 --- a/Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7Engine.cs +++ b/Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7Engine.cs @@ -1,1283 +1,1283 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; -using System.Threading; -using System.Web; -using EnvDTE; -using Microsoft.NodejsTools.Debugger.Communication; -using Microsoft.NodejsTools.Debugger.Remote; -using Microsoft.NodejsTools.Logging; -using Microsoft.NodejsTools.Project; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Debugger.Interop; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudioTools.Project; -using SR = Microsoft.NodejsTools.Project.SR; - -namespace Microsoft.NodejsTools.Debugger.DebugEngine { - // AD7Engine is the primary entrypoint object for the debugging engine. - // - // It implements: - // - // IDebugEngine2: This interface represents a debug engine (DE). It is used to manage various aspects of a debugging session, - // from creating breakpoints to setting and clearing exceptions. - // - // IDebugEngineLaunch2: Used by a debug engine (DE) to launch and terminate programs. - // - // IDebugProgram3: This interface represents a program that is running in a process. Since this engine only debugs one process at a time and each - // process only contains one program, it is implemented on the engine. - - [ComVisible(true)] - [Guid(Guids.DebugEngine)] - public sealed class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugProgram3, IDebugSymbolSettings100 { - // used to send events to the debugger. Some examples of these events are thread create, exception thrown, module load. - private IDebugEventCallback2 _events; - - // The core of the engine is implemented by NodeDebugger - we wrap and expose that to VS. - private NodeDebugger _process; - - // mapping between NodeThread threads and AD7Threads - private readonly Dictionary _threads = new Dictionary(); - private readonly Dictionary _modules = new Dictionary(); - private AD7Thread _mainThread; - private bool _sdmAttached; - private bool _processLoaded; - private bool _loadComplete; - private readonly object _syncLock = new object(); - private bool _attached; - private readonly AutoResetEvent _threadExitedEvent = new AutoResetEvent(false), _processExitedEvent = new AutoResetEvent(false); - private readonly BreakpointManager _breakpointManager; - private Guid _ad7ProgramId; // A unique identifier for the program being debugged. - private static readonly HashSet Engines = new HashSet(); - private string _webBrowserUrl; - - public const string DebugEngineId = "{0A638DAC-429B-4973-ADA0-E8DCDFB29B61}"; - public static Guid DebugEngineGuid = new Guid(DebugEngineId); - private bool _trackFileChanges; - private DocumentEvents _documentEvents; - - /// - /// Specifies whether the process should prompt for input before exiting on an abnormal exit. - /// - public const string WaitOnAbnormalExitSetting = "WAIT_ON_ABNORMAL_EXIT"; - - /// - /// Specifies whether the process should prompt for input before exiting on a normal exit. - /// - public const string WaitOnNormalExitSetting = "WAIT_ON_NORMAL_EXIT"; - - /// - /// Specifies if the output should be redirected to the visual studio output window. - /// - public const string RedirectOutputSetting = "REDIRECT_OUTPUT"; - - /// - /// Specifies options which should be passed to the Node runtime before the script. If - /// the interpreter options should include a semicolon then it should be escaped as a double - /// semi-colon. - /// - public const string InterpreterOptions = "INTERPRETER_OPTIONS"; - - /// - /// Specifies URL to which to open web browser on node debug connect. - /// - public const string WebBrowserUrl = "WEB_BROWSER_URL"; - - /// - /// Specifies the port to be used for the debugger. - /// - public const string DebuggerPort = "DEBUGGER_PORT"; - - /// - /// Specifies a directory mapping in the form of: - /// - /// OldDir|NewDir - /// - /// for mapping between the files on the local machine and the files deployed on the - /// running machine. - /// - public const string DirMappingSetting = "DIR_MAPPING"; - - public AD7Engine() { - LiveLogger.WriteLine("--------------------------------------------------------------------------------"); - LiveLogger.WriteLine("AD7Engine Created ({0})", GetHashCode()); - _breakpointManager = new BreakpointManager(this); - Engines.Add(new WeakReference(this)); - } - - ~AD7Engine() { - LiveLogger.WriteLine("AD7Engine Finalized ({0})", GetHashCode()); - if (!_attached && _process != null) { - // detach the process exited event, we don't need to send the exited event - // which could happen when we terminate the process and check if it's still - // running. - _process.ProcessExited -= OnProcessExited; - - // we launched the process, go ahead and kill it now that - // VS has released us - _process.Terminate(); - } - - foreach (var engine in Engines) { - if (engine.Target == this) { - Engines.Remove(engine); - break; - } - } - } - - internal static IList GetEngines() { - var engines = new List(); - foreach (var engine in Engines) { - var target = (AD7Engine)engine.Target; - if (target != null) { - engines.Add(target); - } - } - return engines; - } - - internal NodeDebugger Process { - get { - return _process; - } - } - - internal AD7Thread MainThread { - get { - return _mainThread; - } - } - - internal BreakpointManager BreakpointManager { - get { - return _breakpointManager; - } - } - - #region IDebugEngine2 Members - - // Attach the debug engine to a program. - int IDebugEngine2.Attach(IDebugProgram2[] rgpPrograms, IDebugProgramNode2[] rgpProgramNodes, uint celtPrograms, IDebugEventCallback2 ad7Callback, enum_ATTACH_REASON dwReason) { - DebugWriteCommand("Attach"); - - AssertMainThread(); - Debug.Assert(_ad7ProgramId == Guid.Empty); - - if (celtPrograms != 1) { - Debug.Fail("Node debugging only supports one program in a process"); - throw new ArgumentException(); - } - - int processId = EngineUtils.GetProcessId(rgpPrograms[0]); - if (processId == 0) { - // engine only supports system processes - LiveLogger.WriteLine("AD7Engine failed to get process id during attach"); - return VSConstants.E_NOTIMPL; - } - - EngineUtils.RequireOk(rgpPrograms[0].GetProgramId(out _ad7ProgramId)); - - // Attach can either be called to attach to a new process, or to complete an attach - // to a launched process - if (_process == null) { - _events = ad7Callback; - - var program = (NodeRemoteDebugProgram)rgpPrograms[0]; - var process = program.DebugProcess; - var uri = process.DebugPort.Uri; - - _process = new NodeDebugger(uri, process.Id); - - // We only need to do fuzzy comparisons when debugging remotely - if (!uri.IsLoopback) { - _process.IsRemote = true; - _process.FileNameMapper = new FuzzyLogicFileNameMapper(EnumerateSolutionFiles()); - } - - AttachEvents(_process); - _attached = true; - } else { - if (processId != _process.Id) { - Debug.Fail("Asked to attach to a process while we are debugging"); - return VSConstants.E_FAIL; - } - } - - lock (_syncLock) { - _sdmAttached = true; - HandleLoadComplete(); - } - - LiveLogger.WriteLine("AD7Engine Attach returning S_OK"); - return VSConstants.S_OK; - } - - private void HandleLoadComplete() { - - // Handle load complete once both sdm attached and process loaded - if (!_sdmAttached || !_processLoaded) { - return; - } - - LiveLogger.WriteLine("Sending load complete ({0})", GetHashCode()); - - AD7EngineCreateEvent.Send(this); - - AD7ProgramCreateEvent.Send(this); - - foreach (var module in _modules.Values) { - SendModuleLoad(module); - } - - foreach (var thread in _threads.Values) { - SendThreadCreate(thread); - } - - lock (_syncLock) { - if (_processLoaded && _process.IsRunning()) { - Send(new AD7LoadCompleteRunningEvent(), AD7LoadCompleteRunningEvent.IID, _mainThread); - } else { - Send(new AD7LoadCompleteEvent(), AD7LoadCompleteEvent.IID, _mainThread); - } - } - - _loadComplete = true; - - if (!String.IsNullOrWhiteSpace(_webBrowserUrl)) { - var uri = new Uri(_webBrowserUrl); - lock (_syncLock) { - OnPortOpenedHandler.CreateHandler( - uri.Port, - shortCircuitPredicate: () => !_processLoaded, - action: LaunchBrowserDebugger - ); - } - } - } - - private void SendThreadCreate(AD7Thread ad7Thread) { - Send(new AD7ThreadCreateEvent(), AD7ThreadCreateEvent.IID, ad7Thread); - } - - private void SendModuleLoad(AD7Module ad7Module) { - var eventObject = new AD7ModuleLoadEvent(ad7Module, true /* this is a module load */); - - // TODO: Bind breakpoints when the module loads - - Send(eventObject, AD7ModuleLoadEvent.IID, null); - } - - // Requests that all programs being debugged by this DE stop execution the next time one of their threads attempts to run. - // This is normally called in response to the user clicking on the pause button in the debugger. - // When the break is complete, an AsyncBreakComplete event will be sent back to the debugger. - int IDebugEngine2.CauseBreak() { - DebugWriteCommand("CauseBreak"); - AssertMainThread(); - return CauseBreak(); - } - - [Conditional("DEBUG")] - private static void AssertMainThread() { - //Debug.Assert(Worker.MainThreadId == Worker.CurrentThreadId); - } - - // Called by the SDM to indicate that a synchronous debug event, previously sent by the DE to the SDM, - // was received and processed. The only event we send in this fashion is Program Destroy. - // It responds to that event by shutting down the engine. - int IDebugEngine2.ContinueFromSynchronousEvent(IDebugEvent2 eventObject) { - DebugWriteCommand("ContinueFromSynchronousEvent"); - AssertMainThread(); - - if (eventObject is AD7ProgramDestroyEvent) { - var debuggedProcess = _process; - - _events = null; - _process = null; - _ad7ProgramId = Guid.Empty; - _threads.Clear(); - _modules.Clear(); - - if (_trackFileChanges) { - _documentEvents.DocumentSaved -= OnDocumentSaved; - _documentEvents = null; - } - - debuggedProcess.Close(); - } else { - Debug.Fail("Unknown synchronous event"); - } - - return VSConstants.S_OK; - } - - // Creates a pending breakpoint in the engine. A pending breakpoint is contains all the information needed to bind a breakpoint to - // a location in the debuggee. - int IDebugEngine2.CreatePendingBreakpoint(IDebugBreakpointRequest2 pBpRequest, out IDebugPendingBreakpoint2 ppPendingBp) { - DebugWriteCommand("CreatePendingBreakpoint"); - Debug.Assert(_breakpointManager != null); - ppPendingBp = null; - - // Check whether breakpoint request for our language - var requestInfo = new BP_REQUEST_INFO[1]; - EngineUtils.CheckOk(pBpRequest.GetRequestInfo(enum_BPREQI_FIELDS.BPREQI_LANGUAGE | enum_BPREQI_FIELDS.BPREQI_BPLOCATION, requestInfo)); - if (requestInfo[0].guidLanguage != Guids.NodejsDebugLanguage && - requestInfo[0].guidLanguage != Guids.ScriptDebugLanguage && - requestInfo[0].guidLanguage != Guids.TypeScriptDebugLanguage) { - // Check whether breakpoint request for our "downloaded" script - // "Downloaded" script will have our IDebugDocument2 - IDebugDocument2 debugDocument; - var debugDocumentPosition = Marshal.GetObjectForIUnknown(requestInfo[0].bpLocation.unionmember2) as IDebugDocumentPosition2; - if (debugDocumentPosition == null || VSConstants.S_OK != debugDocumentPosition.GetDocument(out debugDocument) || null == debugDocument as AD7Document) { - // Not ours - return VSConstants.E_FAIL; - } - } - - _breakpointManager.CreatePendingBreakpoint(pBpRequest, out ppPendingBp); - return VSConstants.S_OK; - } - - // Informs a DE that the program specified has been atypically terminated and that the DE should - // clean up all references to the program and send a program destroy event. - int IDebugEngine2.DestroyProgram(IDebugProgram2 pProgram) { - DebugWriteCommand("DestroyProgram"); - - // Tell the SDM that the engine knows that the program is exiting, and that the - // engine will send a program destroy. We do this because the Win32 debug api will always - // tell us that the process exited, and otherwise we have a race condition. - return (DebuggerConstants.E_PROGRAM_DESTROY_PENDING); - } - - // Gets the GUID of the DE. - int IDebugEngine2.GetEngineId(out Guid guidEngine) { - DebugWriteCommand("GetEngineId"); - guidEngine = DebugEngineGuid; - return VSConstants.S_OK; - } - - private static ExceptionHitTreatment GetExceptionTreatment(enum_EXCEPTION_STATE exceptionState) { - if ((exceptionState & enum_EXCEPTION_STATE.EXCEPTION_STOP_FIRST_CHANCE) != 0) { - return ExceptionHitTreatment.BreakAlways; - } - - // UNDONE Handle break on unhandled, once just my code is supported - // Node has a catch all, so there are no uncaught exceptions - // For now just break always or never - //if ((exceptionState & enum_EXCEPTION_STATE.EXCEPTION_STOP_USER_UNCAUGHT) != 0) - //{ - // return ExceptionHitTreatment.BreakOnUnhandled; - //} - - return ExceptionHitTreatment.BreakNever; - } - - private static void UpdateExceptionTreatment( - IEnumerable exceptionInfos, - Action>> updateExceptionTreatment - ) { - ExceptionHitTreatment? defaultExceptionTreatment = null; - var exceptionTreatments = new List>(); - bool sendUpdate = false; - foreach (var exceptionInfo in exceptionInfos) { - if (exceptionInfo.guidType == DebugEngineGuid) { - sendUpdate = true; - if (exceptionInfo.bstrExceptionName == "Node.js Exceptions") { - defaultExceptionTreatment = GetExceptionTreatment(exceptionInfo.dwState); - } else { - exceptionTreatments.Add(new KeyValuePair(exceptionInfo.bstrExceptionName, GetExceptionTreatment(exceptionInfo.dwState))); - } - } - } - - if (sendUpdate) { - updateExceptionTreatment(defaultExceptionTreatment, exceptionTreatments); - } - } - - int IDebugEngine2.RemoveAllSetExceptions(ref Guid guidType) { - DebugWriteCommand("RemoveAllSetExceptions"); - if (guidType == DebugEngineGuid || guidType == Guid.Empty) { - _process.ClearExceptionTreatment(); - } - return VSConstants.S_OK; - } - - int IDebugEngine2.RemoveSetException(EXCEPTION_INFO[] pException) { - DebugWriteCommand("RemoveSetException"); - UpdateExceptionTreatment(pException, _process.ClearExceptionTreatment); - return VSConstants.S_OK; - } - - int IDebugEngine2.SetException(EXCEPTION_INFO[] pException) { - DebugWriteCommand("SetException"); - UpdateExceptionTreatment(pException, _process.SetExceptionTreatment); - return VSConstants.S_OK; - } - - // Sets the locale of the DE. - // This method is called by the session debug manager (SDM) to propagate the locale settings of the IDE so that - // strings returned by the DE are properly localized. The engine is not localized so this is not implemented. - int IDebugEngine2.SetLocale(ushort wLangId) { - DebugWriteCommand("SetLocale"); - return VSConstants.S_OK; - } - - // A metric is a registry value used to change a debug engine's behavior or to advertise supported functionality. - // This method can forward the call to the appropriate form of the Debugging SDK Helpers function, SetMetric. - int IDebugEngine2.SetMetric(string pszMetric, object varValue) { - DebugWriteCommand("SetMetric"); - return VSConstants.S_OK; - } - - // Sets the registry root currently in use by the DE. Different installations of Visual Studio can change where their registry information is stored - // This allows the debugger to tell the engine where that location is. - int IDebugEngine2.SetRegistryRoot(string pszRegistryRoot) { - DebugWriteCommand("SetRegistryRoot"); - return VSConstants.S_OK; - } - - #endregion - - #region IDebugEngineLaunch2 Members - - // Determines if a process can be terminated. - int IDebugEngineLaunch2.CanTerminateProcess(IDebugProcess2 process) { - DebugWriteCommand("CanTerminateProcess"); - AssertMainThread(); - - Debug.Assert(_events != null); - Debug.Assert(_process != null); - - int processId = EngineUtils.GetProcessId(process); - if (processId == _process.Id) { - return VSConstants.S_OK; - } - - return VSConstants.S_FALSE; - } - - // Launches a process by means of the debug engine. - // Normally, Visual Studio launches a program using the IDebugPortEx2::LaunchSuspended method and then attaches the debugger - // to the suspended program. However, there are circumstances in which the debug engine may need to launch a program - // (for example, if the debug engine is part of an interpreter and the program being debugged is an interpreted language), - // in which case Visual Studio uses the IDebugEngineLaunch2::LaunchSuspended method - // The IDebugEngineLaunch2::ResumeProcess method is called to start the process after the process has been successfully launched in a suspended state. - int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, string exe, string args, string dir, string env, string options, enum_LAUNCH_FLAGS launchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 ad7Callback, out IDebugProcess2 process) { - LiveLogger.WriteLine("AD7Engine LaunchSuspended Called with flags '{0}' ({1})", launchFlags, GetHashCode()); - AssertMainThread(); - - Debug.Assert(_events == null); - Debug.Assert(_process == null); - Debug.Assert(_ad7ProgramId == Guid.Empty); - - _events = ad7Callback; - - var debugOptions = NodeDebugOptions.None; - List dirMapping = null; - string interpreterOptions = null; - ushort? debugPort = null; - if (options != null) { - var splitOptions = SplitOptions(options); - - foreach (var optionSetting in splitOptions) { - var setting = optionSetting.Split(new[] { '=' }, 2); - - if (setting.Length == 2) { - setting[1] = HttpUtility.UrlDecode(setting[1]); - - switch (setting[0]) { - case WaitOnAbnormalExitSetting: - bool value; - if (Boolean.TryParse(setting[1], out value) && value) { - debugOptions |= NodeDebugOptions.WaitOnAbnormalExit; - } - break; - case WaitOnNormalExitSetting: - if (Boolean.TryParse(setting[1], out value) && value) { - debugOptions |= NodeDebugOptions.WaitOnNormalExit; - } - break; - case RedirectOutputSetting: - if (Boolean.TryParse(setting[1], out value) && value) { - debugOptions |= NodeDebugOptions.RedirectOutput; - } - break; - case DirMappingSetting: - string[] dirs = setting[1].Split('|'); - if (dirs.Length == 2) { - if (dirMapping == null) { - dirMapping = new List(); - } - LiveLogger.WriteLine(String.Format("Mapping dir {0} to {1}", dirs[0], dirs[1])); - dirMapping.Add(dirs); - } - break; - case InterpreterOptions: - interpreterOptions = setting[1]; - break; - case WebBrowserUrl: - _webBrowserUrl = setting[1]; - break; - case DebuggerPort: - ushort dbgPortTmp; - if (ushort.TryParse(setting[1], out dbgPortTmp)) { - debugPort = dbgPortTmp; - } - break; - } - } - } - } - - _process = - new NodeDebugger( - exe, - args, - dir, - env, - interpreterOptions, - debugOptions, - debugPort - ); - - _process.Start(false); - - AttachEvents(_process); - - var adProcessId = new AD_PROCESS_ID(); - adProcessId.ProcessIdType = (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_SYSTEM; - adProcessId.dwProcessId = (uint)_process.Id; - - EngineUtils.RequireOk(port.GetProcess(adProcessId, out process)); - LiveLogger.WriteLine("AD7Engine LaunchSuspended returning S_OK"); - Debug.Assert(process != null); - Debug.Assert(!_process.HasExited); - - return VSConstants.S_OK; - } - - private static IEnumerable SplitOptions(string options) { - var res = new List(); - int lastStart = 0; - for (int i = 0; i < options.Length; i++) { - if (options[i] == ';') { - if (i < options.Length - 1 && options[i + 1] != ';') { - // valid option boundary - res.Add(options.Substring(lastStart, i - lastStart)); - lastStart = i + 1; - } else { - i++; - } - } - } - if (options.Length - lastStart > 0) { - res.Add(options.Substring(lastStart, options.Length - lastStart)); - } - return res; - } - - // Resume a process launched by IDebugEngineLaunch2.LaunchSuspended - int IDebugEngineLaunch2.ResumeProcess(IDebugProcess2 process) { - DebugWriteCommand("ResumeProcess"); - AssertMainThread(); - - if (_events == null) { - // process failed to start - LiveLogger.WriteLine("ResumeProcess fails, no events"); - return VSConstants.E_FAIL; - } - - Debug.Assert(_events != null); - Debug.Assert(_process != null); - Debug.Assert(_process != null); - Debug.Assert(_ad7ProgramId == Guid.Empty); - - int processId = EngineUtils.GetProcessId(process); - - if (processId != _process.Id) { - LiveLogger.WriteLine("ResumeProcess fails, wrong process"); - return VSConstants.S_FALSE; - } - - // Send a program node to the SDM. This will cause the SDM to turn around and call IDebugEngine2.Attach - // which will complete the hookup with AD7 - IDebugPort2 port; - EngineUtils.RequireOk(process.GetPort(out port)); - - var defaultPort = (IDebugDefaultPort2)port; - - IDebugPortNotify2 portNotify; - EngineUtils.RequireOk(defaultPort.GetPortNotify(out portNotify)); - - EngineUtils.RequireOk(portNotify.AddProgramNode(new AD7ProgramNode(_process.Id))); - - if (_ad7ProgramId == Guid.Empty) { - LiveLogger.WriteLine("ResumeProcess fails, empty program guid"); - Debug.Fail("Unexpected problem -- IDebugEngine2.Attach wasn't called"); - return VSConstants.E_FAIL; - } - - LiveLogger.WriteLine("ResumeProcess return S_OK"); - return VSConstants.S_OK; - } - - // This function is used to terminate a process that the engine launched - // The debugger will call IDebugEngineLaunch2::CanTerminateProcess before calling this method. - int IDebugEngineLaunch2.TerminateProcess(IDebugProcess2 process) { - DebugWriteCommand("TerminateProcess"); - AssertMainThread(); - - Debug.Assert(_events != null); - Debug.Assert(_process != null); - - int processId = EngineUtils.GetProcessId(process); - if (processId != _process.Id) { - return VSConstants.S_FALSE; - } - - _process.Terminate(); - - return VSConstants.S_OK; - } - - #endregion - - #region IDebugProgram2 Members - - // Determines if a debug engine (DE) can detach from the program. - public int CanDetach() { - DebugWriteCommand("CanDetach"); - return VSConstants.S_OK; - } - - // The debugger calls CauseBreak when the user clicks on the pause button in VS. The debugger should respond by entering - // breakmode. - public int CauseBreak() { - DebugWriteCommand("CauseBreak"); - AssertMainThread(); - - _process.BreakAllAsync().Wait(); - - return VSConstants.S_OK; - } - - // Continue is called from the SDM when it wants execution to continue in the debugee - // but have stepping state remain. An example is when a tracepoint is executed, - // and the debugger does not want to actually enter break mode. - public int Continue(IDebugThread2 pThread) { - AssertMainThread(); - - var thread = (AD7Thread)pThread; - DebugWriteCommand("Continue"); - - // TODO: How does this differ from ExecuteOnThread? - thread.GetDebuggedThread().Resume(); - - return VSConstants.S_OK; - } - - // Detach is called when debugging is stopped and the process was attached to (as opposed to launched) - // or when one of the Detach commands are executed in the UI. - public int Detach() { - DebugWriteCommand("Detach"); - AssertMainThread(); - - _breakpointManager.ClearBreakpointBindingResults(); - - _process.Detach(); - - // Before unregistering event handlers, make sure that we have received thread exit and process exit events, - // since we need to report these as AD7 events to VS to gracefully terminate the debugging session. - _threadExitedEvent.WaitOne(3000); - _processExitedEvent.WaitOne(3000); - - DetachEvents(_process); - _ad7ProgramId = Guid.Empty; - - return VSConstants.S_OK; - } - - // Enumerates the code contexts for a given position in a source file. - public int EnumCodeContexts(IDebugDocumentPosition2 pDocPos, out IEnumDebugCodeContexts2 ppEnum) { - DebugWriteCommand("EnumCodeContexts"); - - string filename; - pDocPos.GetFileName(out filename); - TEXT_POSITION[] beginning = new TEXT_POSITION[1], end = new TEXT_POSITION[1]; - - pDocPos.GetRange(beginning, end); - - ppEnum = new AD7CodeContextEnum(new[] { new AD7MemoryAddress(this, filename, (int)beginning[0].dwLine, (int)beginning[0].dwColumn) }); - return VSConstants.S_OK; - } - - // EnumCodePaths is used for the step-into specific feature -- right click on the current statment and decide which - // function to step into. This is not something that we support. - public int EnumCodePaths(string hint, IDebugCodeContext2 start, IDebugStackFrame2 frame, int fSource, out IEnumCodePaths2 pathEnum, out IDebugCodeContext2 safetyContext) { - DebugWriteCommand("EnumCodePaths"); - - pathEnum = null; - safetyContext = null; - return VSConstants.E_NOTIMPL; - } - - // EnumModules is called by the debugger when it needs to enumerate the modules in the program. - public int EnumModules(out IEnumDebugModules2 ppEnum) { - DebugWriteCommand("EnumModules"); - AssertMainThread(); - - var moduleObjects = new AD7Module[_modules.Count]; - int i = 0; - foreach (var keyValue in _modules) { - var adModule = keyValue.Value; - moduleObjects[i++] = adModule; - } - - ppEnum = new AD7ModuleEnum(moduleObjects); - - return VSConstants.S_OK; - } - - // EnumThreads is called by the debugger when it needs to enumerate the threads in the program. - public int EnumThreads(out IEnumDebugThreads2 ppEnum) { - DebugWriteCommand("EnumThreads"); - AssertMainThread(); - - var threadObjects = new AD7Thread[_threads.Count]; - int i = 0; - foreach (var keyValue in _threads) { - var adThread = keyValue.Value; - - Debug.Assert(adThread != null); - threadObjects[i++] = adThread; - } - - ppEnum = new AD7ThreadEnum(threadObjects); - - return VSConstants.S_OK; - } - - // The properties returned by this method are specific to the program. If the program needs to return more than one property, - // then the IDebugProperty2 object returned by this method is a container of additional properties and calling the - // IDebugProperty2::EnumChildren method returns a list of all properties. - // A program may expose any number and type of additional properties that can be described through the IDebugProperty2 interface. - // An IDE might display the additional program properties through a generic property browser user interface. - public int GetDebugProperty(out IDebugProperty2 ppProperty) { - DebugWriteCommand("GetDebugProperty"); - throw new Exception("The method or operation is not implemented."); - } - - // The debugger calls this when it needs to obtain the IDebugDisassemblyStream2 for a particular code-context. - public int GetDisassemblyStream(enum_DISASSEMBLY_STREAM_SCOPE dwScope, IDebugCodeContext2 codeContext, out IDebugDisassemblyStream2 disassemblyStream) { - DebugWriteCommand("GetDisassemblyStream"); - disassemblyStream = null; - return VSConstants.E_NOTIMPL; - } - - // This method gets the Edit and Continue (ENC) update for this program. A custom debug engine always returns E_NOTIMPL - public int GetENCUpdate(out object update) { - DebugWriteCommand("GetENCUpdate"); - update = null; - return VSConstants.S_OK; - } - - // Gets the name and identifier of the debug engine (DE) running this program. - public int GetEngineInfo(out string engineName, out Guid engineGuid) { - DebugWriteCommand("GetEngineInfo"); - engineName = "Node Engine"; - engineGuid = DebugEngineGuid; - return VSConstants.S_OK; - } - - // The memory bytes as represented by the IDebugMemoryBytes2 object is for the program's image in memory and not any memory - // that was allocated when the program was executed. - public int GetMemoryBytes(out IDebugMemoryBytes2 ppMemoryBytes) { - DebugWriteCommand("GetMemoryBytes"); - throw new Exception("The method or operation is not implemented."); - } - - // Gets the name of the program. - // The name returned by this method is always a friendly, user-displayable name that describes the program. - public int GetName(out string programName) { - // The engine uses default transport and doesn't need to customize the name of the program, - // so return NULL. - programName = null; - return VSConstants.S_OK; - } - - // Gets a GUID for this program. A debug engine (DE) must return the program identifier originally passed to the IDebugProgramNodeAttach2::OnAttach - // or IDebugEngine2::Attach methods. This allows identification of the program across debugger components. - public int GetProgramId(out Guid guidProgramId) { - DebugWriteCommand("GetProgramId"); - guidProgramId = _ad7ProgramId; - return guidProgramId == Guid.Empty ? VSConstants.E_FAIL : VSConstants.S_OK; - } - - // This method is deprecated. Use the IDebugProcess3::Step method instead. - - /// - /// Performs a step. - /// - /// In case there is any thread synchronization or communication between threads, other threads in the program should run when a particular thread is stepping. - /// - public int Step(IDebugThread2 pThread, enum_STEPKIND sk, enum_STEPUNIT step) { - DebugWriteCommand("Step"); - var thread = ((AD7Thread)pThread).GetDebuggedThread(); - switch (sk) { - case enum_STEPKIND.STEP_INTO: thread.StepInto(); break; - case enum_STEPKIND.STEP_OUT: thread.StepOut(); break; - case enum_STEPKIND.STEP_OVER: thread.StepOver(); break; - } - return VSConstants.S_OK; - } - - // Terminates the program. - public int Terminate() { - DebugWriteCommand("Terminate"); - - // Because we implement IDebugEngineLaunch2 we will terminate - // the process in IDebugEngineLaunch2.TerminateProcess - return VSConstants.S_OK; - } - - // Writes a dump to a file. - public int WriteDump(enum_DUMPTYPE dumptype, string pszDumpUrl) { - DebugWriteCommand("WriteDump"); - return VSConstants.E_NOTIMPL; - } - - #endregion - - #region IDebugProgram3 Members - - // ExecuteOnThread is called when the SDM wants execution to continue and have - // stepping state cleared. See http://msdn.microsoft.com/en-us/library/bb145596.aspx for a - // description of different ways we can resume. - public int ExecuteOnThread(IDebugThread2 pThread) { - DebugWriteCommand("ExecuteOnThread"); - AssertMainThread(); - - // clear stepping state on the thread the user was currently on - var thread = (AD7Thread)pThread; - thread.GetDebuggedThread().ClearSteppingState(); - - _process.Resume(); - - return VSConstants.S_OK; - } - - #endregion - - #region IDebugSymbolSettings100 members - - public int SetSymbolLoadState(int bIsManual, int bLoadAdjacent, string strIncludeList, string strExcludeList) { - DebugWriteCommand("SetSymbolLoadState"); - - // The SDM will call this method on the debug engine when it is created, to notify it of the user's - // symbol settings in Tools->Options->Debugging->Symbols. - // - // Params: - // bIsManual: true if 'Automatically load symbols: Only for specified modules' is checked - // bLoadAdjacent: true if 'Specify modules'->'Always load symbols next to the modules' is checked - // strIncludeList: semicolon-delimited list of modules when automatically loading 'Only specified modules' - // strExcludeList: semicolon-delimited list of modules when automatically loading 'All modules, unless excluded' - - return VSConstants.S_OK; - } - - #endregion - - #region Deprecated interface methods - // These methods are not called by the Visual Studio debugger, so they don't need to be implemented - - int IDebugEngine2.EnumPrograms(out IEnumDebugPrograms2 programs) { - Debug.Fail("This function is not called by the debugger"); - - programs = null; - return VSConstants.E_NOTIMPL; - } - - public int Attach(IDebugEventCallback2 pCallback) { - Debug.Fail("This function is not called by the debugger"); - - return VSConstants.E_NOTIMPL; - } - - public int GetProcess(out IDebugProcess2 process) { - Debug.Fail("This function is not called by the debugger"); - - process = null; - return VSConstants.E_NOTIMPL; - } - - public int Execute() { - Debug.Fail("This function is not called by the debugger."); - return VSConstants.E_NOTIMPL; - } - - #endregion - - #region Events - - internal void Send(IDebugEvent2 eventObject, string iidEvent, IDebugProgram2 program, IDebugThread2 thread) { - LiveLogger.WriteLine("AD7Engine Event: {0} ({1})", eventObject.GetType(), iidEvent); - - // Check that events was not disposed - var events = _events; - if (events == null) { - return; - } - - uint attributes; - var riidEvent = new Guid(iidEvent); - - EngineUtils.RequireOk(eventObject.GetAttributes(out attributes)); - - if ((attributes & (uint)enum_EVENTATTRIBUTES.EVENT_STOPPING) != 0 && thread == null) { - Debug.Fail("A thread must be provided for a stopping event"); - return; - } - - try { - EngineUtils.RequireOk(events.Event(this, null, program, thread, eventObject, ref riidEvent, attributes)); - } catch (InvalidCastException) { - // COM object has gone away - } - } - - internal void Send(IDebugEvent2 eventObject, string iidEvent, IDebugThread2 thread) { - Send(eventObject, iidEvent, this, thread); - } - - private void AttachEvents(NodeDebugger process) { - process.ProcessLoaded += OnProcessLoaded; - process.ModuleLoaded += OnModuleLoaded; - process.ThreadCreated += OnThreadCreated; - - process.BreakpointBound += OnBreakpointBound; - process.BreakpointUnbound += OnBreakpointUnbound; - process.BreakpointBindFailure += OnBreakpointBindFailure; - - process.BreakpointHit += OnBreakpointHit; - process.AsyncBreakComplete += OnAsyncBreakComplete; - process.ExceptionRaised += OnExceptionRaised; - process.ProcessExited += OnProcessExited; - process.EntryPointHit += OnEntryPointHit; - process.StepComplete += OnStepComplete; - process.ThreadExited += OnThreadExited; - process.DebuggerOutput += OnDebuggerOutput; - - // Subscribe to document changes if Edit and Continue is enabled. - var shell = (IVsShell)Package.GetGlobalService(typeof(SVsShell)); - if (shell != null) { - // The debug engine is loaded by VS separately from the main NTVS package, so we - // need to make sure that the package is also loaded before querying its options. - var packageGuid = new Guid(Guids.NodejsPackageString); - IVsPackage package; - shell.LoadPackage(ref packageGuid, out package); - - var nodejsPackage = package as NodejsPackage; - if (nodejsPackage != null) { - _trackFileChanges = nodejsPackage.GeneralOptionsPage.EditAndContinue; - - if (_trackFileChanges) { - _documentEvents = nodejsPackage.DTE.Events.DocumentEvents; - _documentEvents.DocumentSaved += OnDocumentSaved; - } - } - } - - process.StartListening(); - } - - private void DetachEvents(NodeDebugger process) { - process.ProcessLoaded -= OnProcessLoaded; - process.ModuleLoaded -= OnModuleLoaded; - process.ThreadCreated -= OnThreadCreated; - - process.BreakpointBound -= OnBreakpointBound; - process.BreakpointUnbound -= OnBreakpointUnbound; - process.BreakpointBindFailure -= OnBreakpointBindFailure; - - process.BreakpointHit -= OnBreakpointHit; - process.AsyncBreakComplete -= OnAsyncBreakComplete; - process.ExceptionRaised -= OnExceptionRaised; - process.ProcessExited -= OnProcessExited; - process.EntryPointHit -= OnEntryPointHit; - process.StepComplete -= OnStepComplete; - process.ThreadExited -= OnThreadExited; - process.DebuggerOutput -= OnDebuggerOutput; - - if (_documentEvents != null) { - _documentEvents.DocumentSaved -= OnDocumentSaved; - } - } - - private void OnThreadExited(object sender, ThreadEventArgs e) { - // TODO: Thread exit code - AD7Thread oldThread; - _threads.TryGetValue(e.Thread, out oldThread); - _threads.Remove(e.Thread); - - _threadExitedEvent.Set(); - - if (oldThread != null) { - Send(new AD7ThreadDestroyEvent(0), AD7ThreadDestroyEvent.IID, oldThread); - } - } - - private void OnThreadCreated(object sender, ThreadEventArgs e) { - LiveLogger.WriteLine("Thread created: " + e.Thread.Id); - - lock (_syncLock) { - var newThread = new AD7Thread(this, e.Thread); - - // Treat first thread created as main thread - // Should only be one for Node - Debug.Assert(_mainThread == null); - if (_mainThread == null) { - _mainThread = newThread; - } - - _threads.Add(e.Thread, newThread); - if (_loadComplete) { - SendThreadCreate(newThread); - } - } - } - - public static List GetDefaultBrowsers() { - var browserList = new List(); - var doc3 = (IVsUIShellOpenDocument3)NodejsPackage.Instance.GetService(typeof(SVsUIShellOpenDocument)); - IVsEnumDocumentPreviewers previewersEnum = doc3.DocumentPreviewersEnum; - - var rgPreviewers = new IVsDocumentPreviewer[1]; - uint celtFetched; - while (ErrorHandler.Succeeded(previewersEnum.Next(1, rgPreviewers, out celtFetched)) && celtFetched == 1) { - if (rgPreviewers[0].IsDefault && !string.IsNullOrEmpty(rgPreviewers[0].Path)) { - browserList.Add(rgPreviewers[0]); - } - } - return browserList; - } - - - private void OnEntryPointHit(object sender, ThreadEventArgs e) { - Send(new AD7EntryPointEvent(), AD7EntryPointEvent.IID, _threads[e.Thread]); - } - - private void LaunchBrowserDebugger() { - LiveLogger.WriteLine("LaunchBrowserDebugger Started"); - - var vsDebugger = (IVsDebugger2)ServiceProvider.GlobalProvider.GetService(typeof(SVsShellDebugger)); - - var info = new VsDebugTargetInfo2(); - var infoSize = Marshal.SizeOf(info); - info.cbSize = (uint)infoSize; - info.bstrExe = _webBrowserUrl; - info.dlo = (uint)_DEBUG_LAUNCH_OPERATION3.DLO_LaunchBrowser; - var defaultBrowsers = GetDefaultBrowsers(); - if (defaultBrowsers.Count != 1 || defaultBrowsers[0].DisplayName != "Internet Explorer") { - // if we use UseDefaultBrowser we lose the nice control & debugging of IE, so - // instead launch w/ no debugging when the user has selected a browser other than IE. - info.LaunchFlags |= (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_StopDebuggingOnEnd | - (uint)__VSDBGLAUNCHFLAGS4.DBGLAUNCH_UseDefaultBrowser | - (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_NoDebug; - } - - info.guidLaunchDebugEngine = DebugEngineGuid; - IntPtr infoPtr = Marshal.AllocCoTaskMem(infoSize); - Marshal.StructureToPtr(info, infoPtr, false); - - try { - vsDebugger.LaunchDebugTargets2(1, infoPtr); - } finally { - if (infoPtr != IntPtr.Zero) { - Marshal.FreeCoTaskMem(infoPtr); - } - } - - LiveLogger.WriteLine("LaunchBrowserDebugger Completed"); - } - - private void OnStepComplete(object sender, ThreadEventArgs e) { - Send(new AD7SteppingCompleteEvent(), AD7SteppingCompleteEvent.IID, _threads[e.Thread]); - } - - private void OnProcessLoaded(object sender, ThreadEventArgs e) { - lock (_syncLock) { - _processLoaded = true; - HandleLoadComplete(); - } - } - - private void OnProcessExited(object sender, ProcessExitedEventArgs e) { - try { - _processExitedEvent.Set(); - lock (_syncLock) { - _processLoaded = false; - Send(new AD7ProgramDestroyEvent((uint)e.ExitCode), AD7ProgramDestroyEvent.IID, null); - } - } catch (InvalidOperationException) { - // we can race at shutdown and deliver the event after the debugger is shutting down. - } - } - - private void OnModuleLoaded(object sender, ModuleLoadedEventArgs e) { - lock (_syncLock) { - var adModule = _modules[e.Module] = new AD7Module(e.Module); - if (_loadComplete) { - SendModuleLoad(adModule); - } - } - } - - private void OnExceptionRaised(object sender, ExceptionRaisedEventArgs e) { - // Exception events are sent when an exception occurs in the debuggee that the debugger was not expecting. - AD7Thread thread; - if (_threads.TryGetValue(e.Thread, out thread)) { - Send( - new AD7DebugExceptionEvent(e.Exception.TypeName, e.Exception.Description, e.IsUnhandled, this), - AD7DebugExceptionEvent.IID, - thread - ); - } - } - - private void OnBreakpointHit(object sender, BreakpointHitEventArgs e) { - var boundBreakpoint = _breakpointManager.GetBoundBreakpoint(e.BreakpointBinding); - Send(new AD7BreakpointEvent(new AD7BoundBreakpointsEnum(new[] { boundBreakpoint })), AD7BreakpointEvent.IID, _threads[e.Thread]); - } - - private void OnBreakpointBound(object sender, BreakpointBindingEventArgs e) { - var pendingBreakpoint = _breakpointManager.GetPendingBreakpoint(e.Breakpoint); - var breakpointBinding = e.BreakpointBinding; - var codeContext = new AD7MemoryAddress(this, pendingBreakpoint.DocumentName, breakpointBinding.Target.Line, breakpointBinding.Target.Column); - var documentContext = new AD7DocumentContext(codeContext); - var breakpointResolution = new AD7BreakpointResolution(this, breakpointBinding, documentContext); - var boundBreakpoint = new AD7BoundBreakpoint(breakpointBinding, pendingBreakpoint, breakpointResolution, breakpointBinding.Enabled); - _breakpointManager.AddBoundBreakpoint(breakpointBinding, boundBreakpoint); - Send( - new AD7BreakpointBoundEvent(pendingBreakpoint, boundBreakpoint), - AD7BreakpointBoundEvent.IID, - null - ); - } - - private void OnBreakpointUnbound(object sender, BreakpointBindingEventArgs e) { - var breakpointBinding = e.BreakpointBinding; - var boundBreakpoint = _breakpointManager.GetBoundBreakpoint(breakpointBinding); - if (boundBreakpoint != null) { - _breakpointManager.RemoveBoundBreakpoint(breakpointBinding); - Send( - new AD7BreakpointUnboundEvent(boundBreakpoint), - AD7BreakpointUnboundEvent.IID, - null - ); - } - } - - private void OnBreakpointBindFailure(object sender, BreakpointBindingEventArgs e) { - var pendingBreakpoint = _breakpointManager.GetPendingBreakpoint(e.Breakpoint); - var breakpointErrorEvent = new AD7BreakpointErrorEvent(pendingBreakpoint, this); - pendingBreakpoint.AddBreakpointError(breakpointErrorEvent); - Send(breakpointErrorEvent, AD7BreakpointErrorEvent.IID, null); - } - - private void OnAsyncBreakComplete(object sender, ThreadEventArgs e) { - AD7Thread thread; - if (!_threads.TryGetValue(e.Thread, out thread)) { - _threads[e.Thread] = thread = new AD7Thread(this, e.Thread); - } - Send(new AD7AsyncBreakCompleteEvent(), AD7AsyncBreakCompleteEvent.IID, thread); - } - - private void OnDebuggerOutput(object sender, OutputEventArgs e) { - AD7Thread thread = null; - if (e.Thread != null && !_threads.TryGetValue(e.Thread, out thread)) { - _threads[e.Thread] = thread = new AD7Thread(this, e.Thread); - } - - // thread can be null for an output string event because it is not - // a stopping event. - Send(new AD7DebugOutputStringEvent2(e.Output), AD7DebugOutputStringEvent2.IID, thread); - } - - private void OnDocumentSaved(Document document) { - var module = Process.GetModuleForFilePath(document.FullName); - if (module == null) { - return; - } - - // For .ts files, we need to build the project to regenerate .js code. - if (String.Equals(Path.GetExtension(module.FileName), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { - if (document.ProjectItem.ContainingProject.GetNodeProject().Build(null, null) != MSBuildResult.Successful) { - var statusBar = (IVsStatusbar)ServiceProvider.GlobalProvider.GetService(typeof(SVsStatusbar)); - statusBar.SetText(SR.GetString(SR.DebuggerModuleUpdateFailed)); - return; - } - } - - DebuggerClient.RunWithRequestExceptionsHandled(async () => { - if (!await Process.UpdateModuleSourceAsync(module).ConfigureAwait(false)) { - var statusBar = (IVsStatusbar)ServiceProvider.GlobalProvider.GetService(typeof(SVsStatusbar)); - statusBar.SetText(SR.GetString(SR.DebuggerModuleUpdateFailed)); - } - }); - } - - #endregion - - internal static void MapLanguageInfo(string filename, out string pbstrLanguage, out Guid pguidLanguage) { - if (String.Equals(Path.GetExtension(filename), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { - pbstrLanguage = NodejsConstants.TypeScript; - pguidLanguage = Guids.TypeScriptDebugLanguage; - } else { - pbstrLanguage = NodejsConstants.JavaScript; - pguidLanguage = Guids.NodejsDebugLanguage; - } - } - - /// - /// Enumerates files in the solution projects. - /// - /// File names collection. - private IEnumerable EnumerateSolutionFiles() { - var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; - if (solution != null) { - foreach (IVsProject project in solution.EnumerateLoadedProjects(false)) { - foreach (uint itemid in project.EnumerateProjectItems()) { - string moniker; - if (ErrorHandler.Succeeded(project.GetMkDocument(itemid, out moniker)) && moniker != null) { - yield return moniker; - } - } - } - } - } - - private void DebugWriteCommand(string commandName) { - LiveLogger.WriteLine("AD7Engine Called " + commandName); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; +using System.Web; +using EnvDTE; +using Microsoft.NodejsTools.Debugger.Communication; +using Microsoft.NodejsTools.Debugger.Remote; +using Microsoft.NodejsTools.Logging; +using Microsoft.NodejsTools.Project; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Debugger.Interop; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudioTools.Project; +using SR = Microsoft.NodejsTools.Project.SR; + +namespace Microsoft.NodejsTools.Debugger.DebugEngine { + // AD7Engine is the primary entrypoint object for the debugging engine. + // + // It implements: + // + // IDebugEngine2: This interface represents a debug engine (DE). It is used to manage various aspects of a debugging session, + // from creating breakpoints to setting and clearing exceptions. + // + // IDebugEngineLaunch2: Used by a debug engine (DE) to launch and terminate programs. + // + // IDebugProgram3: This interface represents a program that is running in a process. Since this engine only debugs one process at a time and each + // process only contains one program, it is implemented on the engine. + + [ComVisible(true)] + [Guid(Guids.DebugEngine)] + public sealed class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugProgram3, IDebugSymbolSettings100 { + // used to send events to the debugger. Some examples of these events are thread create, exception thrown, module load. + private IDebugEventCallback2 _events; + + // The core of the engine is implemented by NodeDebugger - we wrap and expose that to VS. + private NodeDebugger _process; + + // mapping between NodeThread threads and AD7Threads + private readonly Dictionary _threads = new Dictionary(); + private readonly Dictionary _modules = new Dictionary(); + private AD7Thread _mainThread; + private bool _sdmAttached; + private bool _processLoaded; + private bool _loadComplete; + private readonly object _syncLock = new object(); + private bool _attached; + private readonly AutoResetEvent _threadExitedEvent = new AutoResetEvent(false), _processExitedEvent = new AutoResetEvent(false); + private readonly BreakpointManager _breakpointManager; + private Guid _ad7ProgramId; // A unique identifier for the program being debugged. + private static readonly HashSet Engines = new HashSet(); + private string _webBrowserUrl; + + public const string DebugEngineId = "{0A638DAC-429B-4973-ADA0-E8DCDFB29B61}"; + public static Guid DebugEngineGuid = new Guid(DebugEngineId); + private bool _trackFileChanges; + private DocumentEvents _documentEvents; + + /// + /// Specifies whether the process should prompt for input before exiting on an abnormal exit. + /// + public const string WaitOnAbnormalExitSetting = "WAIT_ON_ABNORMAL_EXIT"; + + /// + /// Specifies whether the process should prompt for input before exiting on a normal exit. + /// + public const string WaitOnNormalExitSetting = "WAIT_ON_NORMAL_EXIT"; + + /// + /// Specifies if the output should be redirected to the visual studio output window. + /// + public const string RedirectOutputSetting = "REDIRECT_OUTPUT"; + + /// + /// Specifies options which should be passed to the Node runtime before the script. If + /// the interpreter options should include a semicolon then it should be escaped as a double + /// semi-colon. + /// + public const string InterpreterOptions = "INTERPRETER_OPTIONS"; + + /// + /// Specifies URL to which to open web browser on node debug connect. + /// + public const string WebBrowserUrl = "WEB_BROWSER_URL"; + + /// + /// Specifies the port to be used for the debugger. + /// + public const string DebuggerPort = "DEBUGGER_PORT"; + + /// + /// Specifies a directory mapping in the form of: + /// + /// OldDir|NewDir + /// + /// for mapping between the files on the local machine and the files deployed on the + /// running machine. + /// + public const string DirMappingSetting = "DIR_MAPPING"; + + public AD7Engine() { + LiveLogger.WriteLine("--------------------------------------------------------------------------------"); + LiveLogger.WriteLine("AD7Engine Created ({0})", GetHashCode()); + _breakpointManager = new BreakpointManager(this); + Engines.Add(new WeakReference(this)); + } + + ~AD7Engine() { + LiveLogger.WriteLine("AD7Engine Finalized ({0})", GetHashCode()); + if (!_attached && _process != null) { + // detach the process exited event, we don't need to send the exited event + // which could happen when we terminate the process and check if it's still + // running. + _process.ProcessExited -= OnProcessExited; + + // we launched the process, go ahead and kill it now that + // VS has released us + _process.Terminate(); + } + + foreach (var engine in Engines) { + if (engine.Target == this) { + Engines.Remove(engine); + break; + } + } + } + + internal static IList GetEngines() { + var engines = new List(); + foreach (var engine in Engines) { + var target = (AD7Engine)engine.Target; + if (target != null) { + engines.Add(target); + } + } + return engines; + } + + internal NodeDebugger Process { + get { + return _process; + } + } + + internal AD7Thread MainThread { + get { + return _mainThread; + } + } + + internal BreakpointManager BreakpointManager { + get { + return _breakpointManager; + } + } + + #region IDebugEngine2 Members + + // Attach the debug engine to a program. + int IDebugEngine2.Attach(IDebugProgram2[] rgpPrograms, IDebugProgramNode2[] rgpProgramNodes, uint celtPrograms, IDebugEventCallback2 ad7Callback, enum_ATTACH_REASON dwReason) { + DebugWriteCommand("Attach"); + + AssertMainThread(); + Debug.Assert(_ad7ProgramId == Guid.Empty); + + if (celtPrograms != 1) { + Debug.Fail("Node debugging only supports one program in a process"); + throw new ArgumentException(); + } + + int processId = EngineUtils.GetProcessId(rgpPrograms[0]); + if (processId == 0) { + // engine only supports system processes + LiveLogger.WriteLine("AD7Engine failed to get process id during attach"); + return VSConstants.E_NOTIMPL; + } + + EngineUtils.RequireOk(rgpPrograms[0].GetProgramId(out _ad7ProgramId)); + + // Attach can either be called to attach to a new process, or to complete an attach + // to a launched process + if (_process == null) { + _events = ad7Callback; + + var program = (NodeRemoteDebugProgram)rgpPrograms[0]; + var process = program.DebugProcess; + var uri = process.DebugPort.Uri; + + _process = new NodeDebugger(uri, process.Id); + + // We only need to do fuzzy comparisons when debugging remotely + if (!uri.IsLoopback) { + _process.IsRemote = true; + _process.FileNameMapper = new FuzzyLogicFileNameMapper(EnumerateSolutionFiles()); + } + + AttachEvents(_process); + _attached = true; + } else { + if (processId != _process.Id) { + Debug.Fail("Asked to attach to a process while we are debugging"); + return VSConstants.E_FAIL; + } + } + + lock (_syncLock) { + _sdmAttached = true; + HandleLoadComplete(); + } + + LiveLogger.WriteLine("AD7Engine Attach returning S_OK"); + return VSConstants.S_OK; + } + + private void HandleLoadComplete() { + + // Handle load complete once both sdm attached and process loaded + if (!_sdmAttached || !_processLoaded) { + return; + } + + LiveLogger.WriteLine("Sending load complete ({0})", GetHashCode()); + + AD7EngineCreateEvent.Send(this); + + AD7ProgramCreateEvent.Send(this); + + foreach (var module in _modules.Values) { + SendModuleLoad(module); + } + + foreach (var thread in _threads.Values) { + SendThreadCreate(thread); + } + + lock (_syncLock) { + if (_processLoaded && _process.IsRunning()) { + Send(new AD7LoadCompleteRunningEvent(), AD7LoadCompleteRunningEvent.IID, _mainThread); + } else { + Send(new AD7LoadCompleteEvent(), AD7LoadCompleteEvent.IID, _mainThread); + } + } + + _loadComplete = true; + + if (!String.IsNullOrWhiteSpace(_webBrowserUrl)) { + var uri = new Uri(_webBrowserUrl); + lock (_syncLock) { + OnPortOpenedHandler.CreateHandler( + uri.Port, + shortCircuitPredicate: () => !_processLoaded, + action: LaunchBrowserDebugger + ); + } + } + } + + private void SendThreadCreate(AD7Thread ad7Thread) { + Send(new AD7ThreadCreateEvent(), AD7ThreadCreateEvent.IID, ad7Thread); + } + + private void SendModuleLoad(AD7Module ad7Module) { + var eventObject = new AD7ModuleLoadEvent(ad7Module, true /* this is a module load */); + + // TODO: Bind breakpoints when the module loads + + Send(eventObject, AD7ModuleLoadEvent.IID, null); + } + + // Requests that all programs being debugged by this DE stop execution the next time one of their threads attempts to run. + // This is normally called in response to the user clicking on the pause button in the debugger. + // When the break is complete, an AsyncBreakComplete event will be sent back to the debugger. + int IDebugEngine2.CauseBreak() { + DebugWriteCommand("CauseBreak"); + AssertMainThread(); + return CauseBreak(); + } + + [Conditional("DEBUG")] + private static void AssertMainThread() { + //Debug.Assert(Worker.MainThreadId == Worker.CurrentThreadId); + } + + // Called by the SDM to indicate that a synchronous debug event, previously sent by the DE to the SDM, + // was received and processed. The only event we send in this fashion is Program Destroy. + // It responds to that event by shutting down the engine. + int IDebugEngine2.ContinueFromSynchronousEvent(IDebugEvent2 eventObject) { + DebugWriteCommand("ContinueFromSynchronousEvent"); + AssertMainThread(); + + if (eventObject is AD7ProgramDestroyEvent) { + var debuggedProcess = _process; + + _events = null; + _process = null; + _ad7ProgramId = Guid.Empty; + _threads.Clear(); + _modules.Clear(); + + if (_trackFileChanges) { + _documentEvents.DocumentSaved -= OnDocumentSaved; + _documentEvents = null; + } + + debuggedProcess.Close(); + } else { + Debug.Fail("Unknown synchronous event"); + } + + return VSConstants.S_OK; + } + + // Creates a pending breakpoint in the engine. A pending breakpoint is contains all the information needed to bind a breakpoint to + // a location in the debuggee. + int IDebugEngine2.CreatePendingBreakpoint(IDebugBreakpointRequest2 pBpRequest, out IDebugPendingBreakpoint2 ppPendingBp) { + DebugWriteCommand("CreatePendingBreakpoint"); + Debug.Assert(_breakpointManager != null); + ppPendingBp = null; + + // Check whether breakpoint request for our language + var requestInfo = new BP_REQUEST_INFO[1]; + EngineUtils.CheckOk(pBpRequest.GetRequestInfo(enum_BPREQI_FIELDS.BPREQI_LANGUAGE | enum_BPREQI_FIELDS.BPREQI_BPLOCATION, requestInfo)); + if (requestInfo[0].guidLanguage != Guids.NodejsDebugLanguage && + requestInfo[0].guidLanguage != Guids.ScriptDebugLanguage && + requestInfo[0].guidLanguage != Guids.TypeScriptDebugLanguage) { + // Check whether breakpoint request for our "downloaded" script + // "Downloaded" script will have our IDebugDocument2 + IDebugDocument2 debugDocument; + var debugDocumentPosition = Marshal.GetObjectForIUnknown(requestInfo[0].bpLocation.unionmember2) as IDebugDocumentPosition2; + if (debugDocumentPosition == null || VSConstants.S_OK != debugDocumentPosition.GetDocument(out debugDocument) || null == debugDocument as AD7Document) { + // Not ours + return VSConstants.E_FAIL; + } + } + + _breakpointManager.CreatePendingBreakpoint(pBpRequest, out ppPendingBp); + return VSConstants.S_OK; + } + + // Informs a DE that the program specified has been atypically terminated and that the DE should + // clean up all references to the program and send a program destroy event. + int IDebugEngine2.DestroyProgram(IDebugProgram2 pProgram) { + DebugWriteCommand("DestroyProgram"); + + // Tell the SDM that the engine knows that the program is exiting, and that the + // engine will send a program destroy. We do this because the Win32 debug api will always + // tell us that the process exited, and otherwise we have a race condition. + return (DebuggerConstants.E_PROGRAM_DESTROY_PENDING); + } + + // Gets the GUID of the DE. + int IDebugEngine2.GetEngineId(out Guid guidEngine) { + DebugWriteCommand("GetEngineId"); + guidEngine = DebugEngineGuid; + return VSConstants.S_OK; + } + + private static ExceptionHitTreatment GetExceptionTreatment(enum_EXCEPTION_STATE exceptionState) { + if ((exceptionState & enum_EXCEPTION_STATE.EXCEPTION_STOP_FIRST_CHANCE) != 0) { + return ExceptionHitTreatment.BreakAlways; + } + + // UNDONE Handle break on unhandled, once just my code is supported + // Node has a catch all, so there are no uncaught exceptions + // For now just break always or never + //if ((exceptionState & enum_EXCEPTION_STATE.EXCEPTION_STOP_USER_UNCAUGHT) != 0) + //{ + // return ExceptionHitTreatment.BreakOnUnhandled; + //} + + return ExceptionHitTreatment.BreakNever; + } + + private static void UpdateExceptionTreatment( + IEnumerable exceptionInfos, + Action>> updateExceptionTreatment + ) { + ExceptionHitTreatment? defaultExceptionTreatment = null; + var exceptionTreatments = new List>(); + bool sendUpdate = false; + foreach (var exceptionInfo in exceptionInfos) { + if (exceptionInfo.guidType == DebugEngineGuid) { + sendUpdate = true; + if (exceptionInfo.bstrExceptionName == "Node.js Exceptions") { + defaultExceptionTreatment = GetExceptionTreatment(exceptionInfo.dwState); + } else { + exceptionTreatments.Add(new KeyValuePair(exceptionInfo.bstrExceptionName, GetExceptionTreatment(exceptionInfo.dwState))); + } + } + } + + if (sendUpdate) { + updateExceptionTreatment(defaultExceptionTreatment, exceptionTreatments); + } + } + + int IDebugEngine2.RemoveAllSetExceptions(ref Guid guidType) { + DebugWriteCommand("RemoveAllSetExceptions"); + if (guidType == DebugEngineGuid || guidType == Guid.Empty) { + _process.ClearExceptionTreatment(); + } + return VSConstants.S_OK; + } + + int IDebugEngine2.RemoveSetException(EXCEPTION_INFO[] pException) { + DebugWriteCommand("RemoveSetException"); + UpdateExceptionTreatment(pException, _process.ClearExceptionTreatment); + return VSConstants.S_OK; + } + + int IDebugEngine2.SetException(EXCEPTION_INFO[] pException) { + DebugWriteCommand("SetException"); + UpdateExceptionTreatment(pException, _process.SetExceptionTreatment); + return VSConstants.S_OK; + } + + // Sets the locale of the DE. + // This method is called by the session debug manager (SDM) to propagate the locale settings of the IDE so that + // strings returned by the DE are properly localized. The engine is not localized so this is not implemented. + int IDebugEngine2.SetLocale(ushort wLangId) { + DebugWriteCommand("SetLocale"); + return VSConstants.S_OK; + } + + // A metric is a registry value used to change a debug engine's behavior or to advertise supported functionality. + // This method can forward the call to the appropriate form of the Debugging SDK Helpers function, SetMetric. + int IDebugEngine2.SetMetric(string pszMetric, object varValue) { + DebugWriteCommand("SetMetric"); + return VSConstants.S_OK; + } + + // Sets the registry root currently in use by the DE. Different installations of Visual Studio can change where their registry information is stored + // This allows the debugger to tell the engine where that location is. + int IDebugEngine2.SetRegistryRoot(string pszRegistryRoot) { + DebugWriteCommand("SetRegistryRoot"); + return VSConstants.S_OK; + } + + #endregion + + #region IDebugEngineLaunch2 Members + + // Determines if a process can be terminated. + int IDebugEngineLaunch2.CanTerminateProcess(IDebugProcess2 process) { + DebugWriteCommand("CanTerminateProcess"); + AssertMainThread(); + + Debug.Assert(_events != null); + Debug.Assert(_process != null); + + int processId = EngineUtils.GetProcessId(process); + if (processId == _process.Id) { + return VSConstants.S_OK; + } + + return VSConstants.S_FALSE; + } + + // Launches a process by means of the debug engine. + // Normally, Visual Studio launches a program using the IDebugPortEx2::LaunchSuspended method and then attaches the debugger + // to the suspended program. However, there are circumstances in which the debug engine may need to launch a program + // (for example, if the debug engine is part of an interpreter and the program being debugged is an interpreted language), + // in which case Visual Studio uses the IDebugEngineLaunch2::LaunchSuspended method + // The IDebugEngineLaunch2::ResumeProcess method is called to start the process after the process has been successfully launched in a suspended state. + int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 port, string exe, string args, string dir, string env, string options, enum_LAUNCH_FLAGS launchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 ad7Callback, out IDebugProcess2 process) { + LiveLogger.WriteLine("AD7Engine LaunchSuspended Called with flags '{0}' ({1})", launchFlags, GetHashCode()); + AssertMainThread(); + + Debug.Assert(_events == null); + Debug.Assert(_process == null); + Debug.Assert(_ad7ProgramId == Guid.Empty); + + _events = ad7Callback; + + var debugOptions = NodeDebugOptions.None; + List dirMapping = null; + string interpreterOptions = null; + ushort? debugPort = null; + if (options != null) { + var splitOptions = SplitOptions(options); + + foreach (var optionSetting in splitOptions) { + var setting = optionSetting.Split(new[] { '=' }, 2); + + if (setting.Length == 2) { + setting[1] = HttpUtility.UrlDecode(setting[1]); + + switch (setting[0]) { + case WaitOnAbnormalExitSetting: + bool value; + if (Boolean.TryParse(setting[1], out value) && value) { + debugOptions |= NodeDebugOptions.WaitOnAbnormalExit; + } + break; + case WaitOnNormalExitSetting: + if (Boolean.TryParse(setting[1], out value) && value) { + debugOptions |= NodeDebugOptions.WaitOnNormalExit; + } + break; + case RedirectOutputSetting: + if (Boolean.TryParse(setting[1], out value) && value) { + debugOptions |= NodeDebugOptions.RedirectOutput; + } + break; + case DirMappingSetting: + string[] dirs = setting[1].Split('|'); + if (dirs.Length == 2) { + if (dirMapping == null) { + dirMapping = new List(); + } + LiveLogger.WriteLine(String.Format("Mapping dir {0} to {1}", dirs[0], dirs[1])); + dirMapping.Add(dirs); + } + break; + case InterpreterOptions: + interpreterOptions = setting[1]; + break; + case WebBrowserUrl: + _webBrowserUrl = setting[1]; + break; + case DebuggerPort: + ushort dbgPortTmp; + if (ushort.TryParse(setting[1], out dbgPortTmp)) { + debugPort = dbgPortTmp; + } + break; + } + } + } + } + + _process = + new NodeDebugger( + exe, + args, + dir, + env, + interpreterOptions, + debugOptions, + debugPort + ); + + _process.Start(false); + + AttachEvents(_process); + + var adProcessId = new AD_PROCESS_ID(); + adProcessId.ProcessIdType = (uint)enum_AD_PROCESS_ID.AD_PROCESS_ID_SYSTEM; + adProcessId.dwProcessId = (uint)_process.Id; + + EngineUtils.RequireOk(port.GetProcess(adProcessId, out process)); + LiveLogger.WriteLine("AD7Engine LaunchSuspended returning S_OK"); + Debug.Assert(process != null); + Debug.Assert(!_process.HasExited); + + return VSConstants.S_OK; + } + + private static IEnumerable SplitOptions(string options) { + var res = new List(); + int lastStart = 0; + for (int i = 0; i < options.Length; i++) { + if (options[i] == ';') { + if (i < options.Length - 1 && options[i + 1] != ';') { + // valid option boundary + res.Add(options.Substring(lastStart, i - lastStart)); + lastStart = i + 1; + } else { + i++; + } + } + } + if (options.Length - lastStart > 0) { + res.Add(options.Substring(lastStart, options.Length - lastStart)); + } + return res; + } + + // Resume a process launched by IDebugEngineLaunch2.LaunchSuspended + int IDebugEngineLaunch2.ResumeProcess(IDebugProcess2 process) { + DebugWriteCommand("ResumeProcess"); + AssertMainThread(); + + if (_events == null) { + // process failed to start + LiveLogger.WriteLine("ResumeProcess fails, no events"); + return VSConstants.E_FAIL; + } + + Debug.Assert(_events != null); + Debug.Assert(_process != null); + Debug.Assert(_process != null); + Debug.Assert(_ad7ProgramId == Guid.Empty); + + int processId = EngineUtils.GetProcessId(process); + + if (processId != _process.Id) { + LiveLogger.WriteLine("ResumeProcess fails, wrong process"); + return VSConstants.S_FALSE; + } + + // Send a program node to the SDM. This will cause the SDM to turn around and call IDebugEngine2.Attach + // which will complete the hookup with AD7 + IDebugPort2 port; + EngineUtils.RequireOk(process.GetPort(out port)); + + var defaultPort = (IDebugDefaultPort2)port; + + IDebugPortNotify2 portNotify; + EngineUtils.RequireOk(defaultPort.GetPortNotify(out portNotify)); + + EngineUtils.RequireOk(portNotify.AddProgramNode(new AD7ProgramNode(_process.Id))); + + if (_ad7ProgramId == Guid.Empty) { + LiveLogger.WriteLine("ResumeProcess fails, empty program guid"); + Debug.Fail("Unexpected problem -- IDebugEngine2.Attach wasn't called"); + return VSConstants.E_FAIL; + } + + LiveLogger.WriteLine("ResumeProcess return S_OK"); + return VSConstants.S_OK; + } + + // This function is used to terminate a process that the engine launched + // The debugger will call IDebugEngineLaunch2::CanTerminateProcess before calling this method. + int IDebugEngineLaunch2.TerminateProcess(IDebugProcess2 process) { + DebugWriteCommand("TerminateProcess"); + AssertMainThread(); + + Debug.Assert(_events != null); + Debug.Assert(_process != null); + + int processId = EngineUtils.GetProcessId(process); + if (processId != _process.Id) { + return VSConstants.S_FALSE; + } + + _process.Terminate(); + + return VSConstants.S_OK; + } + + #endregion + + #region IDebugProgram2 Members + + // Determines if a debug engine (DE) can detach from the program. + public int CanDetach() { + DebugWriteCommand("CanDetach"); + return VSConstants.S_OK; + } + + // The debugger calls CauseBreak when the user clicks on the pause button in VS. The debugger should respond by entering + // breakmode. + public int CauseBreak() { + DebugWriteCommand("CauseBreak"); + AssertMainThread(); + + _process.BreakAllAsync().Wait(); + + return VSConstants.S_OK; + } + + // Continue is called from the SDM when it wants execution to continue in the debugee + // but have stepping state remain. An example is when a tracepoint is executed, + // and the debugger does not want to actually enter break mode. + public int Continue(IDebugThread2 pThread) { + AssertMainThread(); + + var thread = (AD7Thread)pThread; + DebugWriteCommand("Continue"); + + // TODO: How does this differ from ExecuteOnThread? + thread.GetDebuggedThread().Resume(); + + return VSConstants.S_OK; + } + + // Detach is called when debugging is stopped and the process was attached to (as opposed to launched) + // or when one of the Detach commands are executed in the UI. + public int Detach() { + DebugWriteCommand("Detach"); + AssertMainThread(); + + _breakpointManager.ClearBreakpointBindingResults(); + + _process.Detach(); + + // Before unregistering event handlers, make sure that we have received thread exit and process exit events, + // since we need to report these as AD7 events to VS to gracefully terminate the debugging session. + _threadExitedEvent.WaitOne(3000); + _processExitedEvent.WaitOne(3000); + + DetachEvents(_process); + _ad7ProgramId = Guid.Empty; + + return VSConstants.S_OK; + } + + // Enumerates the code contexts for a given position in a source file. + public int EnumCodeContexts(IDebugDocumentPosition2 pDocPos, out IEnumDebugCodeContexts2 ppEnum) { + DebugWriteCommand("EnumCodeContexts"); + + string filename; + pDocPos.GetFileName(out filename); + TEXT_POSITION[] beginning = new TEXT_POSITION[1], end = new TEXT_POSITION[1]; + + pDocPos.GetRange(beginning, end); + + ppEnum = new AD7CodeContextEnum(new[] { new AD7MemoryAddress(this, filename, (int)beginning[0].dwLine, (int)beginning[0].dwColumn) }); + return VSConstants.S_OK; + } + + // EnumCodePaths is used for the step-into specific feature -- right click on the current statment and decide which + // function to step into. This is not something that we support. + public int EnumCodePaths(string hint, IDebugCodeContext2 start, IDebugStackFrame2 frame, int fSource, out IEnumCodePaths2 pathEnum, out IDebugCodeContext2 safetyContext) { + DebugWriteCommand("EnumCodePaths"); + + pathEnum = null; + safetyContext = null; + return VSConstants.E_NOTIMPL; + } + + // EnumModules is called by the debugger when it needs to enumerate the modules in the program. + public int EnumModules(out IEnumDebugModules2 ppEnum) { + DebugWriteCommand("EnumModules"); + AssertMainThread(); + + var moduleObjects = new AD7Module[_modules.Count]; + int i = 0; + foreach (var keyValue in _modules) { + var adModule = keyValue.Value; + moduleObjects[i++] = adModule; + } + + ppEnum = new AD7ModuleEnum(moduleObjects); + + return VSConstants.S_OK; + } + + // EnumThreads is called by the debugger when it needs to enumerate the threads in the program. + public int EnumThreads(out IEnumDebugThreads2 ppEnum) { + DebugWriteCommand("EnumThreads"); + AssertMainThread(); + + var threadObjects = new AD7Thread[_threads.Count]; + int i = 0; + foreach (var keyValue in _threads) { + var adThread = keyValue.Value; + + Debug.Assert(adThread != null); + threadObjects[i++] = adThread; + } + + ppEnum = new AD7ThreadEnum(threadObjects); + + return VSConstants.S_OK; + } + + // The properties returned by this method are specific to the program. If the program needs to return more than one property, + // then the IDebugProperty2 object returned by this method is a container of additional properties and calling the + // IDebugProperty2::EnumChildren method returns a list of all properties. + // A program may expose any number and type of additional properties that can be described through the IDebugProperty2 interface. + // An IDE might display the additional program properties through a generic property browser user interface. + public int GetDebugProperty(out IDebugProperty2 ppProperty) { + DebugWriteCommand("GetDebugProperty"); + throw new Exception("The method or operation is not implemented."); + } + + // The debugger calls this when it needs to obtain the IDebugDisassemblyStream2 for a particular code-context. + public int GetDisassemblyStream(enum_DISASSEMBLY_STREAM_SCOPE dwScope, IDebugCodeContext2 codeContext, out IDebugDisassemblyStream2 disassemblyStream) { + DebugWriteCommand("GetDisassemblyStream"); + disassemblyStream = null; + return VSConstants.E_NOTIMPL; + } + + // This method gets the Edit and Continue (ENC) update for this program. A custom debug engine always returns E_NOTIMPL + public int GetENCUpdate(out object update) { + DebugWriteCommand("GetENCUpdate"); + update = null; + return VSConstants.S_OK; + } + + // Gets the name and identifier of the debug engine (DE) running this program. + public int GetEngineInfo(out string engineName, out Guid engineGuid) { + DebugWriteCommand("GetEngineInfo"); + engineName = "Node Engine"; + engineGuid = DebugEngineGuid; + return VSConstants.S_OK; + } + + // The memory bytes as represented by the IDebugMemoryBytes2 object is for the program's image in memory and not any memory + // that was allocated when the program was executed. + public int GetMemoryBytes(out IDebugMemoryBytes2 ppMemoryBytes) { + DebugWriteCommand("GetMemoryBytes"); + throw new Exception("The method or operation is not implemented."); + } + + // Gets the name of the program. + // The name returned by this method is always a friendly, user-displayable name that describes the program. + public int GetName(out string programName) { + // The engine uses default transport and doesn't need to customize the name of the program, + // so return NULL. + programName = null; + return VSConstants.S_OK; + } + + // Gets a GUID for this program. A debug engine (DE) must return the program identifier originally passed to the IDebugProgramNodeAttach2::OnAttach + // or IDebugEngine2::Attach methods. This allows identification of the program across debugger components. + public int GetProgramId(out Guid guidProgramId) { + DebugWriteCommand("GetProgramId"); + guidProgramId = _ad7ProgramId; + return guidProgramId == Guid.Empty ? VSConstants.E_FAIL : VSConstants.S_OK; + } + + // This method is deprecated. Use the IDebugProcess3::Step method instead. + + /// + /// Performs a step. + /// + /// In case there is any thread synchronization or communication between threads, other threads in the program should run when a particular thread is stepping. + /// + public int Step(IDebugThread2 pThread, enum_STEPKIND sk, enum_STEPUNIT step) { + DebugWriteCommand("Step"); + var thread = ((AD7Thread)pThread).GetDebuggedThread(); + switch (sk) { + case enum_STEPKIND.STEP_INTO: thread.StepInto(); break; + case enum_STEPKIND.STEP_OUT: thread.StepOut(); break; + case enum_STEPKIND.STEP_OVER: thread.StepOver(); break; + } + return VSConstants.S_OK; + } + + // Terminates the program. + public int Terminate() { + DebugWriteCommand("Terminate"); + + // Because we implement IDebugEngineLaunch2 we will terminate + // the process in IDebugEngineLaunch2.TerminateProcess + return VSConstants.S_OK; + } + + // Writes a dump to a file. + public int WriteDump(enum_DUMPTYPE dumptype, string pszDumpUrl) { + DebugWriteCommand("WriteDump"); + return VSConstants.E_NOTIMPL; + } + + #endregion + + #region IDebugProgram3 Members + + // ExecuteOnThread is called when the SDM wants execution to continue and have + // stepping state cleared. See http://msdn.microsoft.com/en-us/library/bb145596.aspx for a + // description of different ways we can resume. + public int ExecuteOnThread(IDebugThread2 pThread) { + DebugWriteCommand("ExecuteOnThread"); + AssertMainThread(); + + // clear stepping state on the thread the user was currently on + var thread = (AD7Thread)pThread; + thread.GetDebuggedThread().ClearSteppingState(); + + _process.Resume(); + + return VSConstants.S_OK; + } + + #endregion + + #region IDebugSymbolSettings100 members + + public int SetSymbolLoadState(int bIsManual, int bLoadAdjacent, string strIncludeList, string strExcludeList) { + DebugWriteCommand("SetSymbolLoadState"); + + // The SDM will call this method on the debug engine when it is created, to notify it of the user's + // symbol settings in Tools->Options->Debugging->Symbols. + // + // Params: + // bIsManual: true if 'Automatically load symbols: Only for specified modules' is checked + // bLoadAdjacent: true if 'Specify modules'->'Always load symbols next to the modules' is checked + // strIncludeList: semicolon-delimited list of modules when automatically loading 'Only specified modules' + // strExcludeList: semicolon-delimited list of modules when automatically loading 'All modules, unless excluded' + + return VSConstants.S_OK; + } + + #endregion + + #region Deprecated interface methods + // These methods are not called by the Visual Studio debugger, so they don't need to be implemented + + int IDebugEngine2.EnumPrograms(out IEnumDebugPrograms2 programs) { + Debug.Fail("This function is not called by the debugger"); + + programs = null; + return VSConstants.E_NOTIMPL; + } + + public int Attach(IDebugEventCallback2 pCallback) { + Debug.Fail("This function is not called by the debugger"); + + return VSConstants.E_NOTIMPL; + } + + public int GetProcess(out IDebugProcess2 process) { + Debug.Fail("This function is not called by the debugger"); + + process = null; + return VSConstants.E_NOTIMPL; + } + + public int Execute() { + Debug.Fail("This function is not called by the debugger."); + return VSConstants.E_NOTIMPL; + } + + #endregion + + #region Events + + internal void Send(IDebugEvent2 eventObject, string iidEvent, IDebugProgram2 program, IDebugThread2 thread) { + LiveLogger.WriteLine("AD7Engine Event: {0} ({1})", eventObject.GetType(), iidEvent); + + // Check that events was not disposed + var events = _events; + if (events == null) { + return; + } + + uint attributes; + var riidEvent = new Guid(iidEvent); + + EngineUtils.RequireOk(eventObject.GetAttributes(out attributes)); + + if ((attributes & (uint)enum_EVENTATTRIBUTES.EVENT_STOPPING) != 0 && thread == null) { + Debug.Fail("A thread must be provided for a stopping event"); + return; + } + + try { + EngineUtils.RequireOk(events.Event(this, null, program, thread, eventObject, ref riidEvent, attributes)); + } catch (InvalidCastException) { + // COM object has gone away + } + } + + internal void Send(IDebugEvent2 eventObject, string iidEvent, IDebugThread2 thread) { + Send(eventObject, iidEvent, this, thread); + } + + private void AttachEvents(NodeDebugger process) { + process.ProcessLoaded += OnProcessLoaded; + process.ModuleLoaded += OnModuleLoaded; + process.ThreadCreated += OnThreadCreated; + + process.BreakpointBound += OnBreakpointBound; + process.BreakpointUnbound += OnBreakpointUnbound; + process.BreakpointBindFailure += OnBreakpointBindFailure; + + process.BreakpointHit += OnBreakpointHit; + process.AsyncBreakComplete += OnAsyncBreakComplete; + process.ExceptionRaised += OnExceptionRaised; + process.ProcessExited += OnProcessExited; + process.EntryPointHit += OnEntryPointHit; + process.StepComplete += OnStepComplete; + process.ThreadExited += OnThreadExited; + process.DebuggerOutput += OnDebuggerOutput; + + // Subscribe to document changes if Edit and Continue is enabled. + var shell = (IVsShell)Package.GetGlobalService(typeof(SVsShell)); + if (shell != null) { + // The debug engine is loaded by VS separately from the main NTVS package, so we + // need to make sure that the package is also loaded before querying its options. + var packageGuid = new Guid(Guids.NodejsPackageString); + IVsPackage package; + shell.LoadPackage(ref packageGuid, out package); + + var nodejsPackage = package as NodejsPackage; + if (nodejsPackage != null) { + _trackFileChanges = nodejsPackage.GeneralOptionsPage.EditAndContinue; + + if (_trackFileChanges) { + _documentEvents = nodejsPackage.DTE.Events.DocumentEvents; + _documentEvents.DocumentSaved += OnDocumentSaved; + } + } + } + + process.StartListening(); + } + + private void DetachEvents(NodeDebugger process) { + process.ProcessLoaded -= OnProcessLoaded; + process.ModuleLoaded -= OnModuleLoaded; + process.ThreadCreated -= OnThreadCreated; + + process.BreakpointBound -= OnBreakpointBound; + process.BreakpointUnbound -= OnBreakpointUnbound; + process.BreakpointBindFailure -= OnBreakpointBindFailure; + + process.BreakpointHit -= OnBreakpointHit; + process.AsyncBreakComplete -= OnAsyncBreakComplete; + process.ExceptionRaised -= OnExceptionRaised; + process.ProcessExited -= OnProcessExited; + process.EntryPointHit -= OnEntryPointHit; + process.StepComplete -= OnStepComplete; + process.ThreadExited -= OnThreadExited; + process.DebuggerOutput -= OnDebuggerOutput; + + if (_documentEvents != null) { + _documentEvents.DocumentSaved -= OnDocumentSaved; + } + } + + private void OnThreadExited(object sender, ThreadEventArgs e) { + // TODO: Thread exit code + AD7Thread oldThread; + _threads.TryGetValue(e.Thread, out oldThread); + _threads.Remove(e.Thread); + + _threadExitedEvent.Set(); + + if (oldThread != null) { + Send(new AD7ThreadDestroyEvent(0), AD7ThreadDestroyEvent.IID, oldThread); + } + } + + private void OnThreadCreated(object sender, ThreadEventArgs e) { + LiveLogger.WriteLine("Thread created: " + e.Thread.Id); + + lock (_syncLock) { + var newThread = new AD7Thread(this, e.Thread); + + // Treat first thread created as main thread + // Should only be one for Node + Debug.Assert(_mainThread == null); + if (_mainThread == null) { + _mainThread = newThread; + } + + _threads.Add(e.Thread, newThread); + if (_loadComplete) { + SendThreadCreate(newThread); + } + } + } + + public static List GetDefaultBrowsers() { + var browserList = new List(); + var doc3 = (IVsUIShellOpenDocument3)NodejsPackage.Instance.GetService(typeof(SVsUIShellOpenDocument)); + IVsEnumDocumentPreviewers previewersEnum = doc3.DocumentPreviewersEnum; + + var rgPreviewers = new IVsDocumentPreviewer[1]; + uint celtFetched; + while (ErrorHandler.Succeeded(previewersEnum.Next(1, rgPreviewers, out celtFetched)) && celtFetched == 1) { + if (rgPreviewers[0].IsDefault && !string.IsNullOrEmpty(rgPreviewers[0].Path)) { + browserList.Add(rgPreviewers[0]); + } + } + return browserList; + } + + + private void OnEntryPointHit(object sender, ThreadEventArgs e) { + Send(new AD7EntryPointEvent(), AD7EntryPointEvent.IID, _threads[e.Thread]); + } + + private void LaunchBrowserDebugger() { + LiveLogger.WriteLine("LaunchBrowserDebugger Started"); + + var vsDebugger = (IVsDebugger2)ServiceProvider.GlobalProvider.GetService(typeof(SVsShellDebugger)); + + var info = new VsDebugTargetInfo2(); + var infoSize = Marshal.SizeOf(info); + info.cbSize = (uint)infoSize; + info.bstrExe = _webBrowserUrl; + info.dlo = (uint)_DEBUG_LAUNCH_OPERATION3.DLO_LaunchBrowser; + var defaultBrowsers = GetDefaultBrowsers(); + if (defaultBrowsers.Count != 1 || defaultBrowsers[0].DisplayName != "Internet Explorer") { + // if we use UseDefaultBrowser we lose the nice control & debugging of IE, so + // instead launch w/ no debugging when the user has selected a browser other than IE. + info.LaunchFlags |= (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_StopDebuggingOnEnd | + (uint)__VSDBGLAUNCHFLAGS4.DBGLAUNCH_UseDefaultBrowser | + (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_NoDebug; + } + + info.guidLaunchDebugEngine = DebugEngineGuid; + IntPtr infoPtr = Marshal.AllocCoTaskMem(infoSize); + Marshal.StructureToPtr(info, infoPtr, false); + + try { + vsDebugger.LaunchDebugTargets2(1, infoPtr); + } finally { + if (infoPtr != IntPtr.Zero) { + Marshal.FreeCoTaskMem(infoPtr); + } + } + + LiveLogger.WriteLine("LaunchBrowserDebugger Completed"); + } + + private void OnStepComplete(object sender, ThreadEventArgs e) { + Send(new AD7SteppingCompleteEvent(), AD7SteppingCompleteEvent.IID, _threads[e.Thread]); + } + + private void OnProcessLoaded(object sender, ThreadEventArgs e) { + lock (_syncLock) { + _processLoaded = true; + HandleLoadComplete(); + } + } + + private void OnProcessExited(object sender, ProcessExitedEventArgs e) { + try { + _processExitedEvent.Set(); + lock (_syncLock) { + _processLoaded = false; + Send(new AD7ProgramDestroyEvent((uint)e.ExitCode), AD7ProgramDestroyEvent.IID, null); + } + } catch (InvalidOperationException) { + // we can race at shutdown and deliver the event after the debugger is shutting down. + } + } + + private void OnModuleLoaded(object sender, ModuleLoadedEventArgs e) { + lock (_syncLock) { + var adModule = _modules[e.Module] = new AD7Module(e.Module); + if (_loadComplete) { + SendModuleLoad(adModule); + } + } + } + + private void OnExceptionRaised(object sender, ExceptionRaisedEventArgs e) { + // Exception events are sent when an exception occurs in the debuggee that the debugger was not expecting. + AD7Thread thread; + if (_threads.TryGetValue(e.Thread, out thread)) { + Send( + new AD7DebugExceptionEvent(e.Exception.TypeName, e.Exception.Description, e.IsUnhandled, this), + AD7DebugExceptionEvent.IID, + thread + ); + } + } + + private void OnBreakpointHit(object sender, BreakpointHitEventArgs e) { + var boundBreakpoint = _breakpointManager.GetBoundBreakpoint(e.BreakpointBinding); + Send(new AD7BreakpointEvent(new AD7BoundBreakpointsEnum(new[] { boundBreakpoint })), AD7BreakpointEvent.IID, _threads[e.Thread]); + } + + private void OnBreakpointBound(object sender, BreakpointBindingEventArgs e) { + var pendingBreakpoint = _breakpointManager.GetPendingBreakpoint(e.Breakpoint); + var breakpointBinding = e.BreakpointBinding; + var codeContext = new AD7MemoryAddress(this, pendingBreakpoint.DocumentName, breakpointBinding.Target.Line, breakpointBinding.Target.Column); + var documentContext = new AD7DocumentContext(codeContext); + var breakpointResolution = new AD7BreakpointResolution(this, breakpointBinding, documentContext); + var boundBreakpoint = new AD7BoundBreakpoint(breakpointBinding, pendingBreakpoint, breakpointResolution, breakpointBinding.Enabled); + _breakpointManager.AddBoundBreakpoint(breakpointBinding, boundBreakpoint); + Send( + new AD7BreakpointBoundEvent(pendingBreakpoint, boundBreakpoint), + AD7BreakpointBoundEvent.IID, + null + ); + } + + private void OnBreakpointUnbound(object sender, BreakpointBindingEventArgs e) { + var breakpointBinding = e.BreakpointBinding; + var boundBreakpoint = _breakpointManager.GetBoundBreakpoint(breakpointBinding); + if (boundBreakpoint != null) { + _breakpointManager.RemoveBoundBreakpoint(breakpointBinding); + Send( + new AD7BreakpointUnboundEvent(boundBreakpoint), + AD7BreakpointUnboundEvent.IID, + null + ); + } + } + + private void OnBreakpointBindFailure(object sender, BreakpointBindingEventArgs e) { + var pendingBreakpoint = _breakpointManager.GetPendingBreakpoint(e.Breakpoint); + var breakpointErrorEvent = new AD7BreakpointErrorEvent(pendingBreakpoint, this); + pendingBreakpoint.AddBreakpointError(breakpointErrorEvent); + Send(breakpointErrorEvent, AD7BreakpointErrorEvent.IID, null); + } + + private void OnAsyncBreakComplete(object sender, ThreadEventArgs e) { + AD7Thread thread; + if (!_threads.TryGetValue(e.Thread, out thread)) { + _threads[e.Thread] = thread = new AD7Thread(this, e.Thread); + } + Send(new AD7AsyncBreakCompleteEvent(), AD7AsyncBreakCompleteEvent.IID, thread); + } + + private void OnDebuggerOutput(object sender, OutputEventArgs e) { + AD7Thread thread = null; + if (e.Thread != null && !_threads.TryGetValue(e.Thread, out thread)) { + _threads[e.Thread] = thread = new AD7Thread(this, e.Thread); + } + + // thread can be null for an output string event because it is not + // a stopping event. + Send(new AD7DebugOutputStringEvent2(e.Output), AD7DebugOutputStringEvent2.IID, thread); + } + + private void OnDocumentSaved(Document document) { + var module = Process.GetModuleForFilePath(document.FullName); + if (module == null) { + return; + } + + // For .ts files, we need to build the project to regenerate .js code. + if (String.Equals(Path.GetExtension(module.FileName), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { + if (document.ProjectItem.ContainingProject.GetNodeProject().Build(null, null) != MSBuildResult.Successful) { + var statusBar = (IVsStatusbar)ServiceProvider.GlobalProvider.GetService(typeof(SVsStatusbar)); + statusBar.SetText(SR.GetString(SR.DebuggerModuleUpdateFailed)); + return; + } + } + + DebuggerClient.RunWithRequestExceptionsHandled(async () => { + if (!await Process.UpdateModuleSourceAsync(module).ConfigureAwait(false)) { + var statusBar = (IVsStatusbar)ServiceProvider.GlobalProvider.GetService(typeof(SVsStatusbar)); + statusBar.SetText(SR.GetString(SR.DebuggerModuleUpdateFailed)); + } + }); + } + + #endregion + + internal static void MapLanguageInfo(string filename, out string pbstrLanguage, out Guid pguidLanguage) { + if (String.Equals(Path.GetExtension(filename), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { + pbstrLanguage = NodejsConstants.TypeScript; + pguidLanguage = Guids.TypeScriptDebugLanguage; + } else { + pbstrLanguage = NodejsConstants.JavaScript; + pguidLanguage = Guids.NodejsDebugLanguage; + } + } + + /// + /// Enumerates files in the solution projects. + /// + /// File names collection. + private IEnumerable EnumerateSolutionFiles() { + var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; + if (solution != null) { + foreach (IVsProject project in solution.EnumerateLoadedProjects(false)) { + foreach (uint itemid in project.EnumerateProjectItems()) { + string moniker; + if (ErrorHandler.Succeeded(project.GetMkDocument(itemid, out moniker)) && moniker != null) { + yield return moniker; + } + } + } + } + } + + private void DebugWriteCommand(string commandName) { + LiveLogger.WriteLine("AD7Engine Called " + commandName); + } + } +} diff --git a/Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7ProgramProvider.cs b/Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7ProgramProvider.cs index 237145f85..a0a1882e2 100644 --- a/Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7ProgramProvider.cs +++ b/Nodejs/Product/Nodejs/Debugger/DebugEngine/AD7ProgramProvider.cs @@ -1,120 +1,120 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Runtime.InteropServices; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Debugger.Interop; - -namespace Microsoft.NodejsTools.Debugger.DebugEngine { - // This class implments IDebugProgramProvider2. - // This registered interface allows the session debug manager (SDM) to obtain information about programs - // that have been "published" through the IDebugProgramPublisher2 interface. - [ComVisible(true)] - [Guid(Guids.DebugProgramProvider)] - public class AD7ProgramProvider : IDebugProgramProvider2 { - public AD7ProgramProvider() { - } - - #region IDebugProgramProvider2 Members - - // Obtains information about programs running, filtered in a variety of ways. - int IDebugProgramProvider2.GetProviderProcessData(enum_PROVIDER_FLAGS Flags, IDebugDefaultPort2 port, AD_PROCESS_ID ProcessId, CONST_GUID_ARRAY EngineFilter, PROVIDER_PROCESS_DATA[] processArray) { - - processArray[0] = new PROVIDER_PROCESS_DATA(); - - // we handle creation of the remote program provider ourselves. This is because we always load our program provider locally which keeps - // attach working when developing Python Tools and running/debugging from within VS and in the experimental hive. When we are installed - // we install into the GAC so these types are available to create and then remote debugging works as well. When we're running in the - // experimental hive we are not in the GAC so if we're created outside of VS (e.g. in msvsmon on the local machine) then we can't get - // at our program provider and debug->attach doesn't work. - if (port != null && port.QueryIsLocal() == VSConstants.S_FALSE) { - IDebugCoreServer3 server; - if (ErrorHandler.Succeeded(port.GetServer(out server))) { - IDebugCoreServer90 dbgServer = server as IDebugCoreServer90; - if (dbgServer != null) { - Guid g = typeof(IDebugProgramProvider2).GUID; - IntPtr remoteProviderPunk; - - int hr = dbgServer.CreateManagedInstanceInServer(typeof(AD7ProgramProvider).FullName, typeof(AD7ProgramProvider).Assembly.FullName, 0, ref g, out remoteProviderPunk); - try { - if (ErrorHandler.Succeeded(hr)) { - var remoteProvider = (IDebugProgramProvider2)Marshal.GetObjectForIUnknown(remoteProviderPunk); - return remoteProvider.GetProviderProcessData(Flags, null, ProcessId, EngineFilter, processArray); - } - } finally { - if (remoteProviderPunk != IntPtr.Zero) { - Marshal.Release(remoteProviderPunk); - } - } - } - } - } else if ((Flags & enum_PROVIDER_FLAGS.PFLAG_GET_PROGRAM_NODES) != 0 ) { - // The debugger is asking the engine to return the program nodes it can debug. We check - // each process if it has a python##.dll or python##_d.dll loaded and if it does - // then we report the program as being a Python process. - /* - if (DebugAttach.IsPythonProcess((int)ProcessId.dwProcessId)) { - IDebugProgramNode2 node = new AD7ProgramNode((int)ProcessId.dwProcessId); - - IntPtr[] programNodes = { Marshal.GetComInterfaceForObject(node, typeof(IDebugProgramNode2)) }; - - IntPtr destinationArray = Marshal.AllocCoTaskMem(IntPtr.Size * programNodes.Length); - Marshal.Copy(programNodes, 0, destinationArray, programNodes.Length); - - processArray[0].Fields = enum_PROVIDER_FIELDS.PFIELD_PROGRAM_NODES; - processArray[0].ProgramNodes.Members = destinationArray; - processArray[0].ProgramNodes.dwCount = (uint)programNodes.Length; - - return VSConstants.S_OK; - }*/ - } - - return VSConstants.S_FALSE; - } - - // Gets a program node, given a specific process ID. - int IDebugProgramProvider2.GetProviderProgramNode(enum_PROVIDER_FLAGS Flags, IDebugDefaultPort2 port, AD_PROCESS_ID ProcessId, ref Guid guidEngine, ulong programId, out IDebugProgramNode2 programNode) { - // This method is used for Just-In-Time debugging support, which this program provider does not support - programNode = null; - return VSConstants.E_NOTIMPL; - } - - - // Establishes a locale for any language-specific resources needed by the DE. This engine only supports Enu. - int IDebugProgramProvider2.SetLocale(ushort wLangID) { - return VSConstants.S_OK; - } - - // Establishes a callback to watch for provider events associated with specific kinds of processes - int IDebugProgramProvider2.WatchForProviderEvents(enum_PROVIDER_FLAGS Flags, IDebugDefaultPort2 port, AD_PROCESS_ID ProcessId, CONST_GUID_ARRAY EngineFilter, ref Guid guidLaunchingEngine, IDebugPortNotify2 ad7EventCallback) { - // The sample debug engine is a native debugger, and can therefore always provide a program node - // in GetProviderProcessData. Non-native debuggers may wish to implement this method as a way - // of monitoring the process before code for their runtime starts. For example, if implementing a - // 'foo script' debug engine, one could attach to a process which might eventually run 'foo script' - // before this 'foo script' started. - // - // To implement this method, an engine would monitor the target process and call AddProgramNode - // when the target process started running code which was debuggable by the engine. The - // enum_PROVIDER_FLAGS.PFLAG_ATTACHED_TO_DEBUGGEE flag indicates if the request is to start - // or stop watching the process. - - return VSConstants.S_OK; - } - - #endregion - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Debugger.Interop; + +namespace Microsoft.NodejsTools.Debugger.DebugEngine { + // This class implments IDebugProgramProvider2. + // This registered interface allows the session debug manager (SDM) to obtain information about programs + // that have been "published" through the IDebugProgramPublisher2 interface. + [ComVisible(true)] + [Guid(Guids.DebugProgramProvider)] + public class AD7ProgramProvider : IDebugProgramProvider2 { + public AD7ProgramProvider() { + } + + #region IDebugProgramProvider2 Members + + // Obtains information about programs running, filtered in a variety of ways. + int IDebugProgramProvider2.GetProviderProcessData(enum_PROVIDER_FLAGS Flags, IDebugDefaultPort2 port, AD_PROCESS_ID ProcessId, CONST_GUID_ARRAY EngineFilter, PROVIDER_PROCESS_DATA[] processArray) { + + processArray[0] = new PROVIDER_PROCESS_DATA(); + + // we handle creation of the remote program provider ourselves. This is because we always load our program provider locally which keeps + // attach working when developing Python Tools and running/debugging from within VS and in the experimental hive. When we are installed + // we install into the GAC so these types are available to create and then remote debugging works as well. When we're running in the + // experimental hive we are not in the GAC so if we're created outside of VS (e.g. in msvsmon on the local machine) then we can't get + // at our program provider and debug->attach doesn't work. + if (port != null && port.QueryIsLocal() == VSConstants.S_FALSE) { + IDebugCoreServer3 server; + if (ErrorHandler.Succeeded(port.GetServer(out server))) { + IDebugCoreServer90 dbgServer = server as IDebugCoreServer90; + if (dbgServer != null) { + Guid g = typeof(IDebugProgramProvider2).GUID; + IntPtr remoteProviderPunk; + + int hr = dbgServer.CreateManagedInstanceInServer(typeof(AD7ProgramProvider).FullName, typeof(AD7ProgramProvider).Assembly.FullName, 0, ref g, out remoteProviderPunk); + try { + if (ErrorHandler.Succeeded(hr)) { + var remoteProvider = (IDebugProgramProvider2)Marshal.GetObjectForIUnknown(remoteProviderPunk); + return remoteProvider.GetProviderProcessData(Flags, null, ProcessId, EngineFilter, processArray); + } + } finally { + if (remoteProviderPunk != IntPtr.Zero) { + Marshal.Release(remoteProviderPunk); + } + } + } + } + } else if ((Flags & enum_PROVIDER_FLAGS.PFLAG_GET_PROGRAM_NODES) != 0 ) { + // The debugger is asking the engine to return the program nodes it can debug. We check + // each process if it has a python##.dll or python##_d.dll loaded and if it does + // then we report the program as being a Python process. + /* + if (DebugAttach.IsPythonProcess((int)ProcessId.dwProcessId)) { + IDebugProgramNode2 node = new AD7ProgramNode((int)ProcessId.dwProcessId); + + IntPtr[] programNodes = { Marshal.GetComInterfaceForObject(node, typeof(IDebugProgramNode2)) }; + + IntPtr destinationArray = Marshal.AllocCoTaskMem(IntPtr.Size * programNodes.Length); + Marshal.Copy(programNodes, 0, destinationArray, programNodes.Length); + + processArray[0].Fields = enum_PROVIDER_FIELDS.PFIELD_PROGRAM_NODES; + processArray[0].ProgramNodes.Members = destinationArray; + processArray[0].ProgramNodes.dwCount = (uint)programNodes.Length; + + return VSConstants.S_OK; + }*/ + } + + return VSConstants.S_FALSE; + } + + // Gets a program node, given a specific process ID. + int IDebugProgramProvider2.GetProviderProgramNode(enum_PROVIDER_FLAGS Flags, IDebugDefaultPort2 port, AD_PROCESS_ID ProcessId, ref Guid guidEngine, ulong programId, out IDebugProgramNode2 programNode) { + // This method is used for Just-In-Time debugging support, which this program provider does not support + programNode = null; + return VSConstants.E_NOTIMPL; + } + + + // Establishes a locale for any language-specific resources needed by the DE. This engine only supports Enu. + int IDebugProgramProvider2.SetLocale(ushort wLangID) { + return VSConstants.S_OK; + } + + // Establishes a callback to watch for provider events associated with specific kinds of processes + int IDebugProgramProvider2.WatchForProviderEvents(enum_PROVIDER_FLAGS Flags, IDebugDefaultPort2 port, AD_PROCESS_ID ProcessId, CONST_GUID_ARRAY EngineFilter, ref Guid guidLaunchingEngine, IDebugPortNotify2 ad7EventCallback) { + // The sample debug engine is a native debugger, and can therefore always provide a program node + // in GetProviderProcessData. Non-native debuggers may wish to implement this method as a way + // of monitoring the process before code for their runtime starts. For example, if implementing a + // 'foo script' debug engine, one could attach to a process which might eventually run 'foo script' + // before this 'foo script' started. + // + // To implement this method, an engine would monitor the target process and call AddProgramNode + // when the target process started running code which was debuggable by the engine. The + // enum_PROVIDER_FLAGS.PFLAG_ATTACHED_TO_DEBUGGEE flag indicates if the request is to start + // or stop watching the process. + + return VSConstants.S_OK; + } + + #endregion + } +} diff --git a/Nodejs/Product/Nodejs/Debugger/DebugEngine/Remote/NodeRemoteDebugPortSupplier.cs b/Nodejs/Product/Nodejs/Debugger/DebugEngine/Remote/NodeRemoteDebugPortSupplier.cs index 4df846747..c8ec70e66 100644 --- a/Nodejs/Product/Nodejs/Debugger/DebugEngine/Remote/NodeRemoteDebugPortSupplier.cs +++ b/Nodejs/Product/Nodejs/Debugger/DebugEngine/Remote/NodeRemoteDebugPortSupplier.cs @@ -1,110 +1,110 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Runtime.InteropServices; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Debugger.Interop; - -namespace Microsoft.NodejsTools.Debugger.Remote { - [ComVisible(true)] - [Guid(Guids.RemoteDebugPortSupplier)] - public class NodeRemoteDebugPortSupplier : IDebugPortSupplier2, IDebugPortSupplierDescription2 { - public const string PortSupplierId = "{9E16F805-5EFC-4CE5-8B67-9AE9B643EF80}"; - public static readonly Guid PortSupplierGuid = new Guid(PortSupplierId); - - private static readonly Guid _guid = new Guid(PortSupplierId); - - public NodeRemoteDebugPortSupplier() { - } - - // Qualifier for our transport is parsed either as a tcp://, ws:// or ws:// URI, - // or as 'hostname:port', where ':port' is optional. - public int AddPort(IDebugPortRequest2 pRequest, out IDebugPort2 ppPort) { - ppPort = null; - - string name; - pRequest.GetPortName(out name); - - // Support old-style 'hostname:port' format, as well. - if (!name.Contains("://")) { - name = "tcp://" + name; - } - - var uri = new Uri(name, UriKind.Absolute); - switch (uri.Scheme) { - case "tcp": - // tcp:// URI should only specify host and optionally port, path has no meaning and is invalid. - if (uri.PathAndQuery != "/") { - return new FormatException().HResult; - } - // Set default port if not specified. - if (uri.Port < 0) { - uri = new UriBuilder(uri) { Port = NodejsConstants.DefaultDebuggerPort }.Uri; - } - break; - - case "ws": - case "wss": - // WebSocket URIs are used as is - break; - - default: - // Anything else is not a valid debugger endpoint - return new FormatException().HResult; - } - - ppPort = new NodeRemoteDebugPort(this, pRequest, uri); - return VSConstants.S_OK; - } - - public int CanAddPort() { - return VSConstants.S_OK; - } - - public int EnumPorts(out IEnumDebugPorts2 ppEnum) { - ppEnum = null; - return VSConstants.S_OK; - } - - public int GetPort(ref Guid guidPort, out IDebugPort2 ppPort) { - throw new NotImplementedException(); - } - - public int GetPortSupplierId(out Guid pguidPortSupplier) { - pguidPortSupplier = _guid; - return VSConstants.S_OK; - } - - public int GetPortSupplierName(out string pbstrName) { - pbstrName = "Node.js remote debugging"; - return VSConstants.S_OK; - } - - public int RemovePort(IDebugPort2 pPort) { - return VSConstants.S_OK; - } - - public int GetDescription(enum_PORT_SUPPLIER_DESCRIPTION_FLAGS[] pdwFlags, out string pbstrText) { - pbstrText = - "Allows attaching to Node.js processes running behind a remote debug proxy (RemoteDebug.js). " + - "Related documentation can be found under the 'Tools\\Node.js Tool\\Remote Debug Proxy' menu. " + - "Specify the target hostname and debugger port in the 'Qualifier' textbox, e.g. 'targethost:" + NodejsConstants.DefaultDebuggerPort + "'. " + - "This transport is not secure, and should not be used on a network that might have hostile traffic."; - return VSConstants.S_OK; - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Debugger.Interop; + +namespace Microsoft.NodejsTools.Debugger.Remote { + [ComVisible(true)] + [Guid(Guids.RemoteDebugPortSupplier)] + public class NodeRemoteDebugPortSupplier : IDebugPortSupplier2, IDebugPortSupplierDescription2 { + public const string PortSupplierId = "{9E16F805-5EFC-4CE5-8B67-9AE9B643EF80}"; + public static readonly Guid PortSupplierGuid = new Guid(PortSupplierId); + + private static readonly Guid _guid = new Guid(PortSupplierId); + + public NodeRemoteDebugPortSupplier() { + } + + // Qualifier for our transport is parsed either as a tcp://, ws:// or ws:// URI, + // or as 'hostname:port', where ':port' is optional. + public int AddPort(IDebugPortRequest2 pRequest, out IDebugPort2 ppPort) { + ppPort = null; + + string name; + pRequest.GetPortName(out name); + + // Support old-style 'hostname:port' format, as well. + if (!name.Contains("://")) { + name = "tcp://" + name; + } + + var uri = new Uri(name, UriKind.Absolute); + switch (uri.Scheme) { + case "tcp": + // tcp:// URI should only specify host and optionally port, path has no meaning and is invalid. + if (uri.PathAndQuery != "/") { + return new FormatException().HResult; + } + // Set default port if not specified. + if (uri.Port < 0) { + uri = new UriBuilder(uri) { Port = NodejsConstants.DefaultDebuggerPort }.Uri; + } + break; + + case "ws": + case "wss": + // WebSocket URIs are used as is + break; + + default: + // Anything else is not a valid debugger endpoint + return new FormatException().HResult; + } + + ppPort = new NodeRemoteDebugPort(this, pRequest, uri); + return VSConstants.S_OK; + } + + public int CanAddPort() { + return VSConstants.S_OK; + } + + public int EnumPorts(out IEnumDebugPorts2 ppEnum) { + ppEnum = null; + return VSConstants.S_OK; + } + + public int GetPort(ref Guid guidPort, out IDebugPort2 ppPort) { + throw new NotImplementedException(); + } + + public int GetPortSupplierId(out Guid pguidPortSupplier) { + pguidPortSupplier = _guid; + return VSConstants.S_OK; + } + + public int GetPortSupplierName(out string pbstrName) { + pbstrName = "Node.js remote debugging"; + return VSConstants.S_OK; + } + + public int RemovePort(IDebugPort2 pPort) { + return VSConstants.S_OK; + } + + public int GetDescription(enum_PORT_SUPPLIER_DESCRIPTION_FLAGS[] pdwFlags, out string pbstrText) { + pbstrText = + "Allows attaching to Node.js processes running behind a remote debug proxy (RemoteDebug.js). " + + "Related documentation can be found under the 'Tools\\Node.js Tool\\Remote Debug Proxy' menu. " + + "Specify the target hostname and debugger port in the 'Qualifier' textbox, e.g. 'targethost:" + NodejsConstants.DefaultDebuggerPort + "'. " + + "This transport is not secure, and should not be used on a network that might have hostile traffic."; + return VSConstants.S_OK; + } + } +} diff --git a/Nodejs/Product/Nodejs/Debugger/DebugEngine/Remote/NodeRemoteEnumDebugProcesses.cs b/Nodejs/Product/Nodejs/Debugger/DebugEngine/Remote/NodeRemoteEnumDebugProcesses.cs index 985f6fa09..4a2d02395 100644 --- a/Nodejs/Product/Nodejs/Debugger/DebugEngine/Remote/NodeRemoteEnumDebugProcesses.cs +++ b/Nodejs/Product/Nodejs/Debugger/DebugEngine/Remote/NodeRemoteEnumDebugProcesses.cs @@ -25,7 +25,7 @@ using Microsoft.NodejsTools.Logging; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Debugger.Interop; - + namespace Microsoft.NodejsTools.Debugger.Remote { internal class NodeRemoteEnumDebugProcesses : NodeRemoteEnumDebug, IEnumDebugProcesses2 { private class DebuggerAlreadyAttachedException : Exception { diff --git a/Nodejs/Product/Nodejs/Debugger/NodeDebugger.cs b/Nodejs/Product/Nodejs/Debugger/NodeDebugger.cs index 51cb3cc7e..062571a7b 100644 --- a/Nodejs/Product/Nodejs/Debugger/NodeDebugger.cs +++ b/Nodejs/Product/Nodejs/Debugger/NodeDebugger.cs @@ -243,17 +243,17 @@ public async Task BreakAllAsync() { if (asyncBreakComplete != null) { asyncBreakComplete(this, new ThreadEventArgs(MainThread)); } - } - - internal bool IsRunning() { - var backtraceCommand = new BacktraceCommand(CommandId, _resultFactory, fromFrame: 0, toFrame: 1); - var tokenSource = new CancellationTokenSource(_timeout); - if (TrySendRequestAsync(backtraceCommand, tokenSource.Token).GetAwaiter().GetResult()) { - return backtraceCommand.Running; - } - return false; - } - + } + + internal bool IsRunning() { + var backtraceCommand = new BacktraceCommand(CommandId, _resultFactory, fromFrame: 0, toFrame: 1); + var tokenSource = new CancellationTokenSource(_timeout); + if (TrySendRequestAsync(backtraceCommand, tokenSource.Token).GetAwaiter().GetResult()) { + return backtraceCommand.Running; + } + return false; + } + private void DebugWriteCommand(string commandName) { LiveLogger.WriteLine("NodeDebugger Called " + commandName); } @@ -502,11 +502,11 @@ public void StartListening() { EventHandler newThread = ThreadCreated; if (newThread != null) { newThread(this, new ThreadEventArgs(mainThread)); - } + } EventHandler procLoaded = ProcessLoaded; if (procLoaded != null) { procLoaded(this, new ThreadEventArgs(MainThread)); - } + } } diff --git a/Nodejs/Product/Nodejs/Extensions.cs b/Nodejs/Product/Nodejs/Extensions.cs index 3a9f56b08..96204341f 100644 --- a/Nodejs/Product/Nodejs/Extensions.cs +++ b/Nodejs/Product/Nodejs/Extensions.cs @@ -1,447 +1,447 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Text; -using Microsoft.NodejsTools; -using Microsoft.NodejsTools.Analysis; -using Microsoft.NodejsTools.Classifier; -using Microsoft.NodejsTools.Editor.Core; -using Microsoft.NodejsTools.Intellisense; -using Microsoft.NodejsTools.Project; -using Microsoft.NodejsTools.Repl; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Language.Intellisense; -using Microsoft.VisualStudio.Language.StandardClassification; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Classification; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.Project; - -namespace Microsoft.NodejsTools { - public static class Extensions { - - internal static bool IsPlatformAware(this ProjectNode projectNode) { - string platAwarePropStr = projectNode.BuildProject.GetPropertyValue(ProjectFileConstants.PlatformAware); - bool isPlatformAware = false; - bool.TryParse(platAwarePropStr, out isPlatformAware); - return isPlatformAware; - } - - public static IEnumerable EnumerateLoadedProjects(this IVsSolution solution, bool onlyNodeProjects = true) { - var flags = - onlyNodeProjects ? - (uint)(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION | __VSENUMPROJFLAGS.EPF_MATCHTYPE) : - (uint)(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION | __VSENUMPROJFLAGS.EPF_ALLVIRTUAL); - var guid = new Guid(Guids.NodejsBaseProjectFactoryString); - IEnumHierarchies hierarchies; - ErrorHandler.ThrowOnFailure((solution.GetProjectEnum( - flags, - ref guid, - out hierarchies))); - IVsHierarchy[] hierarchy = new IVsHierarchy[1]; - uint fetched; - while (ErrorHandler.Succeeded(hierarchies.Next(1, hierarchy, out fetched)) && fetched == 1) { - var project = hierarchy[0] as IVsProject; - if (project != null) { - yield return project; - } - } - } - - internal static IEnumerable EnumerateProjectItems(this IVsProject project) { - var enumHierarchyItemsFactory = Package.GetGlobalService(typeof(SVsEnumHierarchyItemsFactory)) as IVsEnumHierarchyItemsFactory; - var hierarchy = (IVsHierarchy)project; - if (enumHierarchyItemsFactory != null && project != null) { - IEnumHierarchyItems enumHierarchyItems; - if (ErrorHandler.Succeeded( - enumHierarchyItemsFactory.EnumHierarchyItems( - hierarchy, - (uint)(__VSEHI.VSEHI_Leaf | __VSEHI.VSEHI_Nest | __VSEHI.VSEHI_OmitHier), - (uint)VSConstants.VSITEMID_ROOT, - out enumHierarchyItems))) { - if (enumHierarchyItems != null) { - VSITEMSELECTION[] rgelt = new VSITEMSELECTION[1]; - uint fetched; - while (VSConstants.S_OK == enumHierarchyItems.Next(1, rgelt, out fetched) && fetched == 1) { - yield return rgelt[0].itemid; - } - } - } - } - } - - internal static NodejsProjectNode GetNodeProject(this EnvDTE.Project project) { - return project.GetCommonProject() as NodejsProjectNode; - } - - internal static EnvDTE.Project GetProject(this IVsHierarchy hierarchy) { - object project; - - ErrorHandler.ThrowOnFailure( - hierarchy.GetProperty( - VSConstants.VSITEMID_ROOT, - (int)__VSHPROPID.VSHPROPID_ExtObject, - out project - ) - ); - - return (project as EnvDTE.Project); - } - - internal static IComponentModel GetComponentModel(this IServiceProvider serviceProvider) { - return (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); - } - - internal static string GetFilePath(this ITextBuffer textBuffer) { - ITextDocument textDocument; - if (textBuffer.Properties.TryGetProperty(typeof(ITextDocument), out textDocument)) { - return textDocument.FilePath; - } else { - return null; - } - } - - internal static T[] Append(this T[] list, T item) { - T[] res = new T[list.Length + 1]; - list.CopyTo(res, 0); - res[res.Length - 1] = item; - return res; - } - - internal static T[] Append(this T[] list, params T[] items) { - T[] res = new T[list.Length + items.Length]; - list.CopyTo(res, 0); - for (int i = 0; i < items.Length; i++) { - res[res.Length - items.Length + i] = items[i]; - } - return res; - } - - internal static EnvDTE.Project GetProject(this ITextBuffer buffer) { - var path = buffer.GetFilePath(); - if (path != null && NodejsPackage.Instance != null) { - foreach (EnvDTE.Project proj in NodejsPackage.Instance.DTE.Solution.Projects) { - var nodeProj = proj.GetNodeProject(); - if (nodeProj != null) { - var nodeFile = nodeProj.FindNodeByFullPath(path); - if (nodeFile != null) { - return proj; - } - } - } - var item = NodejsPackage.Instance.DTE.Solution.FindProjectItem(path); - if (item != null) { - return item.ContainingProject; - } - } - return null; - } - - internal static NodejsProjectNode GetNodejsProject(this EnvDTE.Project project) { - return project.GetCommonProject() as NodejsProjectNode; - } - - internal static VsProjectAnalyzer GetAnalyzer(this ITextView textView) { - NodejsReplEvaluator evaluator; - if (textView.Properties.TryGetProperty(typeof(NodejsReplEvaluator), out evaluator)) { - //return evaluator.ReplAnalyzer; - throw new NotImplementedException("TODO: Repl analysis"); - } - return textView.TextBuffer.GetAnalyzer(); - } - - /// - /// Checks to see if this is a REPL buffer starting with a extensible command such as .cls, .load, etc... - /// - internal static bool IsReplBufferWithCommand(this ITextSnapshot snapshot) { - return snapshot.TextBuffer.Properties.ContainsProperty(typeof(IReplEvaluator)) && - snapshot.Length != 0 && - snapshot[0] == '.'; - } - - internal static VsProjectAnalyzer GetAnalyzer(this ITextBuffer buffer) { - NodejsProjectNode pyProj; - VsProjectAnalyzer analyzer; - if (!buffer.Properties.TryGetProperty(typeof(NodejsProjectNode), out pyProj)) { - var project = buffer.GetProject(); - if (project != null) { - pyProj = project.GetNodejsProject(); - if (pyProj != null) { - buffer.Properties.AddProperty(typeof(NodejsProjectNode), pyProj); - } - } - } - - if (pyProj != null) { - analyzer = pyProj.Analyzer; - return analyzer; - } - - // exists for tests where we don't run in VS and for the existing changes preview - if (buffer.Properties.TryGetProperty(typeof(VsProjectAnalyzer), out analyzer)) { - return analyzer; - } - - if (NodejsPackage.Instance == null) { - // The REPL is open on restart, but our package isn't loaded yet. Force - // it to load now. - var shell = (IVsShell)NodejsPackage.GetGlobalService(typeof(SVsShell)); - Guid packageGuid = typeof(NodejsPackage).GUID; - IVsPackage package; - if (ErrorHandler.Failed(shell.LoadPackage(ref packageGuid, out package))) { - return null; - } - Debug.Assert(NodejsPackage.Instance != null); - } - return NodejsPackage.Instance.DefaultAnalyzer; - } - - internal static bool TryGetJsProjectEntry(this ITextBuffer buffer, out IJsProjectEntry entry) { - IProjectEntry e; - if (buffer.TryGetProjectEntry(out e) && (entry = e as IJsProjectEntry) != null) { - return true; - } - entry = null; - return false; - } - - internal static IProjectEntry GetProjectEntry(this ITextBuffer buffer) { - IProjectEntry res; - buffer.TryGetProjectEntry(out res); - return res; - } - - internal static bool TryGetProjectEntry(this ITextBuffer buffer, out IProjectEntry entry) { - return buffer.Properties.TryGetProperty(typeof(IProjectEntry), out entry); - } - - internal static string LimitLines( - this string str, - int maxLines = 30, - int charsPerLine = 200, - bool ellipsisAtEnd = true, - bool stopAtFirstBlankLine = false - ) { - if (string.IsNullOrEmpty(str)) { - return str; - } - - int lineCount = 0; - var prettyPrinted = new StringBuilder(); - bool wasEmpty = true; - - using (var reader = new StringReader(str)) { - for (var line = reader.ReadLine(); line != null && lineCount < maxLines; line = reader.ReadLine()) { - if (string.IsNullOrWhiteSpace(line)) { - if (wasEmpty) { - continue; - } - wasEmpty = true; - if (stopAtFirstBlankLine) { - lineCount = maxLines; - break; - } - lineCount += 1; - prettyPrinted.AppendLine(); - } else { - wasEmpty = false; - lineCount += (line.Length / charsPerLine) + 1; - prettyPrinted.AppendLine(line); - } - } - } - if (ellipsisAtEnd && lineCount >= maxLines) { - prettyPrinted.AppendLine("..."); - } - return prettyPrinted.ToString().Trim(); - } - - internal static bool CanComplete(this ClassificationSpan token) { - return token.ClassificationType.IsOfType(PredefinedClassificationTypeNames.Keyword) | - token.ClassificationType.IsOfType(PredefinedClassificationTypeNames.Identifier); - } - - internal static bool IsOpenGrouping(this ClassificationSpan span) { - return span.ClassificationType.IsOfType(NodejsPredefinedClassificationTypeNames.Grouping) && - span.Span.Length == 1 && - (span.Span.GetText() == "{" || span.Span.GetText() == "[" || span.Span.GetText() == "("); - } - - internal static bool IsCloseGrouping(this ClassificationSpan span) { - return span.ClassificationType.IsOfType(NodejsPredefinedClassificationTypeNames.Grouping) && - span.Span.Length == 1 && - (span.Span.GetText() == "}" || span.Span.GetText() == "]" || span.Span.GetText() == ")"); - } - - internal static void GotoSource(this LocationInfo location) { - NodejsPackage.NavigateTo( - location.FilePath, - location.Line - 1, - location.Column - 1 - ); - } - - internal static SnapshotPoint? GetCaretPosition(this ITextView view) { - return view.BufferGraph.MapDownToFirstMatch( - new SnapshotPoint(view.TextBuffer.CurrentSnapshot, view.Caret.Position.BufferPosition), - PointTrackingMode.Positive, - EditorExtensions.IsNodeJsContent, - PositionAffinity.Successor - ); - } - - private static bool IsIdentifierChar(char curChar) { - return Char.IsLetterOrDigit(curChar) || curChar == '_' || curChar == '$'; - } - - internal static ITrackingSpan GetCaretSpan(this ITextView view) { - var caretPoint = view.GetCaretPosition(); - Debug.Assert(caretPoint != null); - var snapshot = caretPoint.Value.Snapshot; - var caretPos = caretPoint.Value.Position; - - // fob( - // ^ - // +--- Caret here - // - // We want to lookup fob, not fob( - // - ITrackingSpan span; - if (caretPos != snapshot.Length) { - string curChar = snapshot.GetText(caretPos, 1); - if (!IsIdentifierChar(curChar[0]) && caretPos > 0) { - string prevChar = snapshot.GetText(caretPos - 1, 1); - if (IsIdentifierChar(prevChar[0])) { - caretPos--; - } - } - span = snapshot.CreateTrackingSpan( - caretPos, - 1, - SpanTrackingMode.EdgeInclusive - ); - } else { - span = snapshot.CreateTrackingSpan( - caretPos, - 0, - SpanTrackingMode.EdgeInclusive - ); - } - - return span; - } - - internal static ITrackingSpan CreateTrackingSpan(this IQuickInfoSession session, ITextBuffer buffer) { - var triggerPoint = session.GetTriggerPoint(buffer); - var position = triggerPoint.GetPosition(buffer.CurrentSnapshot); - if (position == buffer.CurrentSnapshot.Length) { - return ((IIntellisenseSession)session).GetApplicableSpan(buffer); - } - - return buffer.CurrentSnapshot.CreateTrackingSpan(position, 1, SpanTrackingMode.EdgeInclusive); - } - - /// - /// Returns the span to use for the provided intellisense session. - /// - /// A tracking span. The span may be of length zero if there - /// is no suitable token at the trigger point. - internal static ITrackingSpan GetApplicableSpan(this IIntellisenseSession session, ITextBuffer buffer) { - var snapshot = buffer.CurrentSnapshot; - var triggerPoint = session.GetTriggerPoint(buffer); - - var span = snapshot.GetApplicableSpan(triggerPoint); - if (span != null) { - return span; - } - return snapshot.CreateTrackingSpan(triggerPoint.GetPosition(snapshot), 0, SpanTrackingMode.EdgeInclusive); - } - - /// - /// Returns the applicable span at the provided position. - /// - /// A tracking span, or null if there is no token at the - /// provided position. - internal static ITrackingSpan GetApplicableSpan(this ITextSnapshot snapshot, ITrackingPoint point) { - return snapshot.GetApplicableSpan(point.GetPosition(snapshot)); - } - - /// - /// Returns the applicable span at the provided position. - /// - /// A tracking span, or null if there is no token at the - /// provided position. - internal static ITrackingSpan GetApplicableSpan(this ITextSnapshot snapshot, int position) { - var classifier = snapshot.TextBuffer.GetNodejsClassifier(); - var line = snapshot.GetLineFromPosition(position); - if (classifier == null || line == null) { - return null; - } - - var spanLength = position - line.Start.Position; - // Increase position by one to include 'fob' in: "abc.|fob" - if (spanLength < line.Length) { - spanLength += 1; - } - - var classifications = classifier.GetClassificationSpans(new SnapshotSpan(line.Start, spanLength)); - // Handle "|" - if (classifications == null || classifications.Count == 0) { - return null; - } - - var lastToken = classifications[classifications.Count - 1]; - // Handle "fob |" - if (lastToken == null || position > lastToken.Span.End) { - return null; - } - - if (position > lastToken.Span.Start) { - if (lastToken.CanComplete()) { - // Handle "fo|o" - return snapshot.CreateTrackingSpan(lastToken.Span, SpanTrackingMode.EdgeInclusive); - } else { - // Handle "<|=" - return null; - } - } - - var secondLastToken = classifications.Count >= 2 ? classifications[classifications.Count - 2] : null; - if (lastToken.Span.Start == position && lastToken.CanComplete() && - (secondLastToken == null || // Handle "|fob" - position > secondLastToken.Span.End || // Handle "if |fob" - !secondLastToken.CanComplete())) { // Handle "abc.|fob" - return snapshot.CreateTrackingSpan(lastToken.Span, SpanTrackingMode.EdgeInclusive); - } - - // Handle "abc|." - // ("ab|c." would have been treated as "ab|c") - if (secondLastToken != null && secondLastToken.Span.End == position && secondLastToken.CanComplete()) { - return snapshot.CreateTrackingSpan(secondLastToken.Span, SpanTrackingMode.EdgeInclusive); - } - - return null; - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using Microsoft.NodejsTools; +using Microsoft.NodejsTools.Analysis; +using Microsoft.NodejsTools.Classifier; +using Microsoft.NodejsTools.Editor.Core; +using Microsoft.NodejsTools.Intellisense; +using Microsoft.NodejsTools.Project; +using Microsoft.NodejsTools.Repl; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Language.StandardClassification; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.Project; + +namespace Microsoft.NodejsTools { + public static class Extensions { + + internal static bool IsPlatformAware(this ProjectNode projectNode) { + string platAwarePropStr = projectNode.BuildProject.GetPropertyValue(ProjectFileConstants.PlatformAware); + bool isPlatformAware = false; + bool.TryParse(platAwarePropStr, out isPlatformAware); + return isPlatformAware; + } + + public static IEnumerable EnumerateLoadedProjects(this IVsSolution solution, bool onlyNodeProjects = true) { + var flags = + onlyNodeProjects ? + (uint)(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION | __VSENUMPROJFLAGS.EPF_MATCHTYPE) : + (uint)(__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION | __VSENUMPROJFLAGS.EPF_ALLVIRTUAL); + var guid = new Guid(Guids.NodejsBaseProjectFactoryString); + IEnumHierarchies hierarchies; + ErrorHandler.ThrowOnFailure((solution.GetProjectEnum( + flags, + ref guid, + out hierarchies))); + IVsHierarchy[] hierarchy = new IVsHierarchy[1]; + uint fetched; + while (ErrorHandler.Succeeded(hierarchies.Next(1, hierarchy, out fetched)) && fetched == 1) { + var project = hierarchy[0] as IVsProject; + if (project != null) { + yield return project; + } + } + } + + internal static IEnumerable EnumerateProjectItems(this IVsProject project) { + var enumHierarchyItemsFactory = Package.GetGlobalService(typeof(SVsEnumHierarchyItemsFactory)) as IVsEnumHierarchyItemsFactory; + var hierarchy = (IVsHierarchy)project; + if (enumHierarchyItemsFactory != null && project != null) { + IEnumHierarchyItems enumHierarchyItems; + if (ErrorHandler.Succeeded( + enumHierarchyItemsFactory.EnumHierarchyItems( + hierarchy, + (uint)(__VSEHI.VSEHI_Leaf | __VSEHI.VSEHI_Nest | __VSEHI.VSEHI_OmitHier), + (uint)VSConstants.VSITEMID_ROOT, + out enumHierarchyItems))) { + if (enumHierarchyItems != null) { + VSITEMSELECTION[] rgelt = new VSITEMSELECTION[1]; + uint fetched; + while (VSConstants.S_OK == enumHierarchyItems.Next(1, rgelt, out fetched) && fetched == 1) { + yield return rgelt[0].itemid; + } + } + } + } + } + + internal static NodejsProjectNode GetNodeProject(this EnvDTE.Project project) { + return project.GetCommonProject() as NodejsProjectNode; + } + + internal static EnvDTE.Project GetProject(this IVsHierarchy hierarchy) { + object project; + + ErrorHandler.ThrowOnFailure( + hierarchy.GetProperty( + VSConstants.VSITEMID_ROOT, + (int)__VSHPROPID.VSHPROPID_ExtObject, + out project + ) + ); + + return (project as EnvDTE.Project); + } + + internal static IComponentModel GetComponentModel(this IServiceProvider serviceProvider) { + return (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); + } + + internal static string GetFilePath(this ITextBuffer textBuffer) { + ITextDocument textDocument; + if (textBuffer.Properties.TryGetProperty(typeof(ITextDocument), out textDocument)) { + return textDocument.FilePath; + } else { + return null; + } + } + + internal static T[] Append(this T[] list, T item) { + T[] res = new T[list.Length + 1]; + list.CopyTo(res, 0); + res[res.Length - 1] = item; + return res; + } + + internal static T[] Append(this T[] list, params T[] items) { + T[] res = new T[list.Length + items.Length]; + list.CopyTo(res, 0); + for (int i = 0; i < items.Length; i++) { + res[res.Length - items.Length + i] = items[i]; + } + return res; + } + + internal static EnvDTE.Project GetProject(this ITextBuffer buffer) { + var path = buffer.GetFilePath(); + if (path != null && NodejsPackage.Instance != null) { + foreach (EnvDTE.Project proj in NodejsPackage.Instance.DTE.Solution.Projects) { + var nodeProj = proj.GetNodeProject(); + if (nodeProj != null) { + var nodeFile = nodeProj.FindNodeByFullPath(path); + if (nodeFile != null) { + return proj; + } + } + } + var item = NodejsPackage.Instance.DTE.Solution.FindProjectItem(path); + if (item != null) { + return item.ContainingProject; + } + } + return null; + } + + internal static NodejsProjectNode GetNodejsProject(this EnvDTE.Project project) { + return project.GetCommonProject() as NodejsProjectNode; + } + + internal static VsProjectAnalyzer GetAnalyzer(this ITextView textView) { + NodejsReplEvaluator evaluator; + if (textView.Properties.TryGetProperty(typeof(NodejsReplEvaluator), out evaluator)) { + //return evaluator.ReplAnalyzer; + throw new NotImplementedException("TODO: Repl analysis"); + } + return textView.TextBuffer.GetAnalyzer(); + } + + /// + /// Checks to see if this is a REPL buffer starting with a extensible command such as .cls, .load, etc... + /// + internal static bool IsReplBufferWithCommand(this ITextSnapshot snapshot) { + return snapshot.TextBuffer.Properties.ContainsProperty(typeof(IReplEvaluator)) && + snapshot.Length != 0 && + snapshot[0] == '.'; + } + + internal static VsProjectAnalyzer GetAnalyzer(this ITextBuffer buffer) { + NodejsProjectNode pyProj; + VsProjectAnalyzer analyzer; + if (!buffer.Properties.TryGetProperty(typeof(NodejsProjectNode), out pyProj)) { + var project = buffer.GetProject(); + if (project != null) { + pyProj = project.GetNodejsProject(); + if (pyProj != null) { + buffer.Properties.AddProperty(typeof(NodejsProjectNode), pyProj); + } + } + } + + if (pyProj != null) { + analyzer = pyProj.Analyzer; + return analyzer; + } + + // exists for tests where we don't run in VS and for the existing changes preview + if (buffer.Properties.TryGetProperty(typeof(VsProjectAnalyzer), out analyzer)) { + return analyzer; + } + + if (NodejsPackage.Instance == null) { + // The REPL is open on restart, but our package isn't loaded yet. Force + // it to load now. + var shell = (IVsShell)NodejsPackage.GetGlobalService(typeof(SVsShell)); + Guid packageGuid = typeof(NodejsPackage).GUID; + IVsPackage package; + if (ErrorHandler.Failed(shell.LoadPackage(ref packageGuid, out package))) { + return null; + } + Debug.Assert(NodejsPackage.Instance != null); + } + return NodejsPackage.Instance.DefaultAnalyzer; + } + + internal static bool TryGetJsProjectEntry(this ITextBuffer buffer, out IJsProjectEntry entry) { + IProjectEntry e; + if (buffer.TryGetProjectEntry(out e) && (entry = e as IJsProjectEntry) != null) { + return true; + } + entry = null; + return false; + } + + internal static IProjectEntry GetProjectEntry(this ITextBuffer buffer) { + IProjectEntry res; + buffer.TryGetProjectEntry(out res); + return res; + } + + internal static bool TryGetProjectEntry(this ITextBuffer buffer, out IProjectEntry entry) { + return buffer.Properties.TryGetProperty(typeof(IProjectEntry), out entry); + } + + internal static string LimitLines( + this string str, + int maxLines = 30, + int charsPerLine = 200, + bool ellipsisAtEnd = true, + bool stopAtFirstBlankLine = false + ) { + if (string.IsNullOrEmpty(str)) { + return str; + } + + int lineCount = 0; + var prettyPrinted = new StringBuilder(); + bool wasEmpty = true; + + using (var reader = new StringReader(str)) { + for (var line = reader.ReadLine(); line != null && lineCount < maxLines; line = reader.ReadLine()) { + if (string.IsNullOrWhiteSpace(line)) { + if (wasEmpty) { + continue; + } + wasEmpty = true; + if (stopAtFirstBlankLine) { + lineCount = maxLines; + break; + } + lineCount += 1; + prettyPrinted.AppendLine(); + } else { + wasEmpty = false; + lineCount += (line.Length / charsPerLine) + 1; + prettyPrinted.AppendLine(line); + } + } + } + if (ellipsisAtEnd && lineCount >= maxLines) { + prettyPrinted.AppendLine("..."); + } + return prettyPrinted.ToString().Trim(); + } + + internal static bool CanComplete(this ClassificationSpan token) { + return token.ClassificationType.IsOfType(PredefinedClassificationTypeNames.Keyword) | + token.ClassificationType.IsOfType(PredefinedClassificationTypeNames.Identifier); + } + + internal static bool IsOpenGrouping(this ClassificationSpan span) { + return span.ClassificationType.IsOfType(NodejsPredefinedClassificationTypeNames.Grouping) && + span.Span.Length == 1 && + (span.Span.GetText() == "{" || span.Span.GetText() == "[" || span.Span.GetText() == "("); + } + + internal static bool IsCloseGrouping(this ClassificationSpan span) { + return span.ClassificationType.IsOfType(NodejsPredefinedClassificationTypeNames.Grouping) && + span.Span.Length == 1 && + (span.Span.GetText() == "}" || span.Span.GetText() == "]" || span.Span.GetText() == ")"); + } + + internal static void GotoSource(this LocationInfo location) { + NodejsPackage.NavigateTo( + location.FilePath, + location.Line - 1, + location.Column - 1 + ); + } + + internal static SnapshotPoint? GetCaretPosition(this ITextView view) { + return view.BufferGraph.MapDownToFirstMatch( + new SnapshotPoint(view.TextBuffer.CurrentSnapshot, view.Caret.Position.BufferPosition), + PointTrackingMode.Positive, + EditorExtensions.IsNodeJsContent, + PositionAffinity.Successor + ); + } + + private static bool IsIdentifierChar(char curChar) { + return Char.IsLetterOrDigit(curChar) || curChar == '_' || curChar == '$'; + } + + internal static ITrackingSpan GetCaretSpan(this ITextView view) { + var caretPoint = view.GetCaretPosition(); + Debug.Assert(caretPoint != null); + var snapshot = caretPoint.Value.Snapshot; + var caretPos = caretPoint.Value.Position; + + // fob( + // ^ + // +--- Caret here + // + // We want to lookup fob, not fob( + // + ITrackingSpan span; + if (caretPos != snapshot.Length) { + string curChar = snapshot.GetText(caretPos, 1); + if (!IsIdentifierChar(curChar[0]) && caretPos > 0) { + string prevChar = snapshot.GetText(caretPos - 1, 1); + if (IsIdentifierChar(prevChar[0])) { + caretPos--; + } + } + span = snapshot.CreateTrackingSpan( + caretPos, + 1, + SpanTrackingMode.EdgeInclusive + ); + } else { + span = snapshot.CreateTrackingSpan( + caretPos, + 0, + SpanTrackingMode.EdgeInclusive + ); + } + + return span; + } + + internal static ITrackingSpan CreateTrackingSpan(this IQuickInfoSession session, ITextBuffer buffer) { + var triggerPoint = session.GetTriggerPoint(buffer); + var position = triggerPoint.GetPosition(buffer.CurrentSnapshot); + if (position == buffer.CurrentSnapshot.Length) { + return ((IIntellisenseSession)session).GetApplicableSpan(buffer); + } + + return buffer.CurrentSnapshot.CreateTrackingSpan(position, 1, SpanTrackingMode.EdgeInclusive); + } + + /// + /// Returns the span to use for the provided intellisense session. + /// + /// A tracking span. The span may be of length zero if there + /// is no suitable token at the trigger point. + internal static ITrackingSpan GetApplicableSpan(this IIntellisenseSession session, ITextBuffer buffer) { + var snapshot = buffer.CurrentSnapshot; + var triggerPoint = session.GetTriggerPoint(buffer); + + var span = snapshot.GetApplicableSpan(triggerPoint); + if (span != null) { + return span; + } + return snapshot.CreateTrackingSpan(triggerPoint.GetPosition(snapshot), 0, SpanTrackingMode.EdgeInclusive); + } + + /// + /// Returns the applicable span at the provided position. + /// + /// A tracking span, or null if there is no token at the + /// provided position. + internal static ITrackingSpan GetApplicableSpan(this ITextSnapshot snapshot, ITrackingPoint point) { + return snapshot.GetApplicableSpan(point.GetPosition(snapshot)); + } + + /// + /// Returns the applicable span at the provided position. + /// + /// A tracking span, or null if there is no token at the + /// provided position. + internal static ITrackingSpan GetApplicableSpan(this ITextSnapshot snapshot, int position) { + var classifier = snapshot.TextBuffer.GetNodejsClassifier(); + var line = snapshot.GetLineFromPosition(position); + if (classifier == null || line == null) { + return null; + } + + var spanLength = position - line.Start.Position; + // Increase position by one to include 'fob' in: "abc.|fob" + if (spanLength < line.Length) { + spanLength += 1; + } + + var classifications = classifier.GetClassificationSpans(new SnapshotSpan(line.Start, spanLength)); + // Handle "|" + if (classifications == null || classifications.Count == 0) { + return null; + } + + var lastToken = classifications[classifications.Count - 1]; + // Handle "fob |" + if (lastToken == null || position > lastToken.Span.End) { + return null; + } + + if (position > lastToken.Span.Start) { + if (lastToken.CanComplete()) { + // Handle "fo|o" + return snapshot.CreateTrackingSpan(lastToken.Span, SpanTrackingMode.EdgeInclusive); + } else { + // Handle "<|=" + return null; + } + } + + var secondLastToken = classifications.Count >= 2 ? classifications[classifications.Count - 2] : null; + if (lastToken.Span.Start == position && lastToken.CanComplete() && + (secondLastToken == null || // Handle "|fob" + position > secondLastToken.Span.End || // Handle "if |fob" + !secondLastToken.CanComplete())) { // Handle "abc.|fob" + return snapshot.CreateTrackingSpan(lastToken.Span, SpanTrackingMode.EdgeInclusive); + } + + // Handle "abc|." + // ("ab|c." would have been treated as "ab|c") + if (secondLastToken != null && secondLastToken.Span.End == position && secondLastToken.CanComplete()) { + return snapshot.CreateTrackingSpan(secondLastToken.Span, SpanTrackingMode.EdgeInclusive); + } + + return null; + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Nodejs/Guids.cs b/Nodejs/Product/Nodejs/Guids.cs index 51169ca5f..4cd2dca70 100644 --- a/Nodejs/Product/Nodejs/Guids.cs +++ b/Nodejs/Product/Nodejs/Guids.cs @@ -1,81 +1,81 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -// Guids.cs -// MUST match guids.h -using System; - -namespace Microsoft.NodejsTools -{ - static class Guids - { - public const string NodejsPackageString = "FE8A8C3D-328A-476D-99F9-2A24B75F8C7F"; - public const string NodejsCmdSetString = "695e37e2-c6df-4e0a-8833-f688e4c65f1f"; - public const string NodejsDebugLanguageString = "{65791609-BA29-49CF-A214-DBFF8AEC3BC2}"; - public const string NodejsEditorFactoryString = "88941496-93F4-4E37-83AF-AFE087415334"; - public const string NodejsEditorFactoryPromptEncodingString = "C8576E92-EFB6-4414-8F63-C84D474A539E"; - //do not remove the curly braces. Without curly braces, in certain cases some language service features (e.g. snippets) will fail to load because - //some comparisons in native code surround the guid string with curlies, and they'll fail to match unless we also surround the guid string with curlies. - public const string NodejsLanguageInfoString = "{ABD5E8A5-5A35-4BE9-BCAF-E10C1212CB40}"; - public const string NodejsNpmCmdSetString = "9F4B31B4-09AC-4937-A2E7-F4BC02BB7DBA"; - public const string NodejsProjectFactoryString = "3AF33F2E-1136-4D97-BBB7-1795711AC8B8"; - public const string NodejsBaseProjectFactoryString = "9092AA53-FB77-4645-B42D-1CCCA6BD08BD"; - public const string TypeScriptLanguageInfoString = "87bdf188-e6e8-4fcf-a82a-9b8506e01847"; - public const string JadeEditorFactoryString = "6CB69EF8-1329-4DC0-84B4-FA134EA59BE3"; - public const string DefaultLanguageServiceString = "{8239BEC4-EE87-11D0-8C98-00C04FC2AB22}"; - - internal static readonly Guid DefaultLanguageService = new Guid(DefaultLanguageServiceString); - internal static readonly Guid NodejsLanguageInfo = new Guid(NodejsLanguageInfoString); - - //Guid for our formatting service - internal const string JavaScriptFormattingServiceString = "F414C260-6AC0-11CF-B6D1-00AA00BBBB58"; - - public const string ScriptDebugLanguageString = "{F7FA31DA-C32A-11D0-B442-00A0244A1DD2}"; - - // Profiling guids - public const string NodejsProfilingPkgString = "B515653F-FB69-4B64-9D3F-F1FCF8421DD0"; - public const string NodejsProfilingCmdSetString = "3F2BC93C-CA2D-450B-9BFC-0C96288F1ED6"; - public const string ProfilingEditorFactoryString = "3585dc22-81a0-409e-85ae-cae5d02d99cd"; - - // Debug guids - public const string DebugEngine = "FC5B45BA-5B9C-46EA-887A-82073AE065FE"; - public const string DebugProgramProvider = "472CD331-218C-451E-929E-98C9408F11DD"; - public const string RemoteDebugPortSupplier = "A241707C-7DB3-464F-8D3E-F3D33E86AE99"; - - public static readonly Guid NodejsBaseProjectFactory = new Guid(NodejsBaseProjectFactoryString); - public static readonly Guid NodejsCmdSet = new Guid(NodejsCmdSetString); - public static readonly Guid NodejsEditorFactory = new Guid(NodejsEditorFactoryString); - public static readonly Guid NodejsDebugLanguage = new Guid(NodejsDebugLanguageString); - public static readonly Guid NodejsNpmCmdSet = new Guid(NodejsNpmCmdSetString); - public static readonly Guid TypeScriptDebugLanguage = new Guid(TypeScriptLanguageInfoString); - - public static readonly Guid ScriptDebugLanguage = new Guid(ScriptDebugLanguageString); - - public static readonly Guid VenusCommandId = new Guid("c7547851-4e3a-4e5b-9173-fa6e9c8bd82c"); - public static readonly Guid Eureka = new Guid("30947ebe-9147-45f9-96cf-401bfc671a82"); // Microsoft.VisualStudio.Web.Eureka.dll package, includes page inspector - public static readonly Guid WebPackageCommandId = new Guid("822e3603-e573-47d2-acf0-520e4ce641c2"); - public static readonly Guid WebPackage = new Guid("d9a342d1-a429-4059-808a-e55ee6351f7f"); - public static readonly Guid WebAppCmdId = new Guid("CB26E292-901A-419c-B79D-49BD45C43929"); - - public static readonly Guid NodejsProfilingCmdSet = new Guid(NodejsProfilingCmdSetString); - public static readonly Guid VsUIHierarchyWindow = new Guid("{7D960B07-7AF8-11D0-8E5E-00A0C911005A}"); - public static readonly Guid ProfilingEditorFactory = new Guid(ProfilingEditorFactoryString); - public static readonly Guid PerfPkg = new Guid("{F4A63B2A-49AB-4b2d-AA59-A10F01026C89}"); - - public const string OfficeToolsBootstrapperCmdSetString = "{D26C976C-8EE8-4EC4-8746-F5F7702A17C5}"; - public static readonly Guid OfficeToolsBootstrapperCmdSet = new Guid(OfficeToolsBootstrapperCmdSetString); - }; +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +// Guids.cs +// MUST match guids.h +using System; + +namespace Microsoft.NodejsTools +{ + static class Guids + { + public const string NodejsPackageString = "FE8A8C3D-328A-476D-99F9-2A24B75F8C7F"; + public const string NodejsCmdSetString = "695e37e2-c6df-4e0a-8833-f688e4c65f1f"; + public const string NodejsDebugLanguageString = "{65791609-BA29-49CF-A214-DBFF8AEC3BC2}"; + public const string NodejsEditorFactoryString = "88941496-93F4-4E37-83AF-AFE087415334"; + public const string NodejsEditorFactoryPromptEncodingString = "C8576E92-EFB6-4414-8F63-C84D474A539E"; + //do not remove the curly braces. Without curly braces, in certain cases some language service features (e.g. snippets) will fail to load because + //some comparisons in native code surround the guid string with curlies, and they'll fail to match unless we also surround the guid string with curlies. + public const string NodejsLanguageInfoString = "{ABD5E8A5-5A35-4BE9-BCAF-E10C1212CB40}"; + public const string NodejsNpmCmdSetString = "9F4B31B4-09AC-4937-A2E7-F4BC02BB7DBA"; + public const string NodejsProjectFactoryString = "3AF33F2E-1136-4D97-BBB7-1795711AC8B8"; + public const string NodejsBaseProjectFactoryString = "9092AA53-FB77-4645-B42D-1CCCA6BD08BD"; + public const string TypeScriptLanguageInfoString = "87bdf188-e6e8-4fcf-a82a-9b8506e01847"; + public const string JadeEditorFactoryString = "6CB69EF8-1329-4DC0-84B4-FA134EA59BE3"; + public const string DefaultLanguageServiceString = "{8239BEC4-EE87-11D0-8C98-00C04FC2AB22}"; + + internal static readonly Guid DefaultLanguageService = new Guid(DefaultLanguageServiceString); + internal static readonly Guid NodejsLanguageInfo = new Guid(NodejsLanguageInfoString); + + //Guid for our formatting service + internal const string JavaScriptFormattingServiceString = "F414C260-6AC0-11CF-B6D1-00AA00BBBB58"; + + public const string ScriptDebugLanguageString = "{F7FA31DA-C32A-11D0-B442-00A0244A1DD2}"; + + // Profiling guids + public const string NodejsProfilingPkgString = "B515653F-FB69-4B64-9D3F-F1FCF8421DD0"; + public const string NodejsProfilingCmdSetString = "3F2BC93C-CA2D-450B-9BFC-0C96288F1ED6"; + public const string ProfilingEditorFactoryString = "3585dc22-81a0-409e-85ae-cae5d02d99cd"; + + // Debug guids + public const string DebugEngine = "FC5B45BA-5B9C-46EA-887A-82073AE065FE"; + public const string DebugProgramProvider = "472CD331-218C-451E-929E-98C9408F11DD"; + public const string RemoteDebugPortSupplier = "A241707C-7DB3-464F-8D3E-F3D33E86AE99"; + + public static readonly Guid NodejsBaseProjectFactory = new Guid(NodejsBaseProjectFactoryString); + public static readonly Guid NodejsCmdSet = new Guid(NodejsCmdSetString); + public static readonly Guid NodejsEditorFactory = new Guid(NodejsEditorFactoryString); + public static readonly Guid NodejsDebugLanguage = new Guid(NodejsDebugLanguageString); + public static readonly Guid NodejsNpmCmdSet = new Guid(NodejsNpmCmdSetString); + public static readonly Guid TypeScriptDebugLanguage = new Guid(TypeScriptLanguageInfoString); + + public static readonly Guid ScriptDebugLanguage = new Guid(ScriptDebugLanguageString); + + public static readonly Guid VenusCommandId = new Guid("c7547851-4e3a-4e5b-9173-fa6e9c8bd82c"); + public static readonly Guid Eureka = new Guid("30947ebe-9147-45f9-96cf-401bfc671a82"); // Microsoft.VisualStudio.Web.Eureka.dll package, includes page inspector + public static readonly Guid WebPackageCommandId = new Guid("822e3603-e573-47d2-acf0-520e4ce641c2"); + public static readonly Guid WebPackage = new Guid("d9a342d1-a429-4059-808a-e55ee6351f7f"); + public static readonly Guid WebAppCmdId = new Guid("CB26E292-901A-419c-B79D-49BD45C43929"); + + public static readonly Guid NodejsProfilingCmdSet = new Guid(NodejsProfilingCmdSetString); + public static readonly Guid VsUIHierarchyWindow = new Guid("{7D960B07-7AF8-11D0-8E5E-00A0C911005A}"); + public static readonly Guid ProfilingEditorFactory = new Guid(ProfilingEditorFactoryString); + public static readonly Guid PerfPkg = new Guid("{F4A63B2A-49AB-4b2d-AA59-A10F01026C89}"); + + public const string OfficeToolsBootstrapperCmdSetString = "{D26C976C-8EE8-4EC4-8746-F5F7702A17C5}"; + public static readonly Guid OfficeToolsBootstrapperCmdSet = new Guid(OfficeToolsBootstrapperCmdSetString); + }; } \ No newline at end of file diff --git a/Nodejs/Product/Nodejs/Intellisense/ExpansionClient.cs b/Nodejs/Product/Nodejs/Intellisense/ExpansionClient.cs index 394c1fd3c..2f0ac964b 100644 --- a/Nodejs/Product/Nodejs/Intellisense/ExpansionClient.cs +++ b/Nodejs/Product/Nodejs/Intellisense/ExpansionClient.cs @@ -1,305 +1,305 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; -using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudioTools; -using MSXML; - -namespace Microsoft.NodejsTools.Intellisense { - internal sealed class ExpansionClient : IVsExpansionClient { - internal IServiceProvider serviceProvider; - - private readonly IVsTextLines _lines; - private readonly IVsExpansion _expansion; - private readonly IVsTextView _view; - private readonly ITextView _textView; - private readonly IVsEditorAdaptersFactoryService _adapterFactory; - private readonly IServiceProvider _serviceProvider; - private IVsExpansionSession _session; - private bool _sessionEnded, _selectEndSpan; - private ITrackingPoint _selectionStart, _selectionEnd; - - public const string SurroundsWith = "SurroundsWith"; - public const string Expansion = "Expansion"; - - public ExpansionClient(ITextView textView, IVsEditorAdaptersFactoryService adapterFactory, IServiceProvider serviceProvider) { - _textView = textView; - _serviceProvider = serviceProvider; - _adapterFactory = adapterFactory; - _view = adapterFactory.GetViewAdapter(textView); - _lines = (IVsTextLines)adapterFactory.GetBufferAdapter(textView.TextBuffer); - _expansion = _lines as IVsExpansion; - if (_expansion == null) { - throw new ArgumentException("TextBuffer does not support expansions"); - } - } - - public bool InSession { - get { - return _session != null; - } - } - - public int EndExpansion() { - _session = null; - _sessionEnded = true; - _selectionStart = _selectionEnd = null; - return VSConstants.S_OK; - } - - public int FormatSpan(IVsTextLines pBuffer, TextSpan[] ts) { - IXMLDOMNode codeNode, snippetTypes, declarations; - int hr; - if (ErrorHandler.Failed(hr = _session.GetSnippetNode("CodeSnippet:Code", out codeNode))) { - return hr; - } - - if (ErrorHandler.Failed(hr = _session.GetHeaderNode("CodeSnippet:SnippetTypes", out snippetTypes))) { - return hr; - } - - List declList = new List(); - if (ErrorHandler.Succeeded(hr = _session.GetSnippetNode("CodeSnippet:Declarations", out declarations)) - && declarations != null) { - foreach (IXMLDOMNode declType in declarations.childNodes) { - var id = declType.selectSingleNode("./CodeSnippet:ID"); - if (id != null) { - declList.Add(id.text); - } - } - } - - bool surroundsWith = false; - foreach (MSXML.IXMLDOMNode snippetType in snippetTypes.childNodes) { - if (snippetType.nodeName == "SnippetType") { - if (snippetType.text == SurroundsWith) { - surroundsWith = true; - } - } - } - // get the indentation of where we're inserting the code... - string baseIndentation = GetBaseIndentation(ts); - - using (var edit = _textView.TextBuffer.CreateEdit()) { - if (surroundsWith) { - var templateText = codeNode.text.Replace("\r\n", VsExtensions.GetNewLineText(_textView.TextSnapshot)); - foreach (var decl in declList) { - string defaultValue; - if (ErrorHandler.Succeeded(_session.GetFieldValue(decl, out defaultValue))) { - templateText = templateText.Replace("$" + decl + "$", defaultValue); - } - } - - templateText = templateText.Replace("$end$", ""); - // we can finally figure out where the selected text began witin the original template... - int selectedIndex = templateText.IndexOf("$selected$"); - if (selectedIndex != -1) { - var selection = _textView.Selection; - - // now we need to get the indentation of the $selected$ element within the template, - // as we'll need to indent the selected code to that level. - string indentation = GetTemplateSelectionIndentation(templateText, selectedIndex); - - var start = _selectionStart.GetPosition(_textView.TextBuffer.CurrentSnapshot); - var end = _selectionEnd.GetPosition(_textView.TextBuffer.CurrentSnapshot); - if (end < start) { - // we didn't actually have a selection, and our negative tracking pushed us - // back to the start of the buffer... - end = start; - } - var selectedSpan = Span.FromBounds(start, end); - - if (surroundsWith) { - if (string.IsNullOrWhiteSpace(_textView.TextBuffer.CurrentSnapshot.GetText(selectedSpan))) { - - // Surround With can be invoked with no selection, but on a line with some text. - // In that case we need to inject an extra new line. - var endLine = _textView.TextBuffer.CurrentSnapshot.GetLineFromPosition(end); - var endText = endLine.GetText().Substring(end - endLine.Start); - if (!String.IsNullOrWhiteSpace(endText)) { - edit.Insert(end, _textView.Options.GetNewLineCharacter()); - } - - } else { - _selectEndSpan = true; - } - - } - - IndentSpan( - edit, - indentation, - _textView.TextBuffer.CurrentSnapshot.GetLineFromPosition(start).LineNumber + 1, // 1st line is already indented - _textView.TextBuffer.CurrentSnapshot.GetLineFromPosition(end).LineNumber - ); - } - } - - // we now need to update any code which was not selected that we just inserted. - IndentSpan(edit, baseIndentation, ts[0].iStartLine + 1, ts[0].iEndLine); - - edit.Apply(); - - } - - return hr; - } - - public int InsertNamedExpansion(string pszTitle, string pszPath, TextSpan textSpan) { - if (_session != null) { - // if the user starts an expansion session while one is in progress - // then abort the current expansion session - _session.EndCurrentExpansion(1); - _session = null; - } - - var selection = _textView.Selection; - var snapshot = selection.Start.Position.Snapshot; - - _selectionStart = snapshot.CreateTrackingPoint(selection.Start.Position, VisualStudio.Text.PointTrackingMode.Positive); - _selectionEnd = snapshot.CreateTrackingPoint(selection.End.Position, VisualStudio.Text.PointTrackingMode.Negative); - _selectEndSpan = _sessionEnded = false; - - int hr = _expansion.InsertNamedExpansion( - pszTitle, - pszPath, - textSpan, - this, - Guids.NodejsLanguageInfo, - 0, - out _session - ); - - if (ErrorHandler.Succeeded(hr)) { - if (_sessionEnded) { - _session = null; - } - } - return hr; - } - - private static string GetTemplateSelectionIndentation(string templateText, int selectedIndex) { - string indentation = ""; - for (int i = selectedIndex - 1; i >= 0; i--) { - if (templateText[i] != '\t' && templateText[i] != ' ') { - indentation = templateText.Substring(i + 1, selectedIndex - i - 1); - break; - } - } - return indentation; - } - - private string GetBaseIndentation(TextSpan[] ts) { - var indentationLine = _textView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(ts[0].iStartLine).GetText(); - string baseIndentation = indentationLine; - for (int i = 0; i < indentationLine.Length; i++) { - if (indentationLine[i] != ' ' && indentationLine[i] != '\t') { - baseIndentation = indentationLine.Substring(0, i); - break; - } - } - return baseIndentation; - } - - private void IndentSpan(ITextEdit edit, string indentation, int startLine, int endLine) { - var snapshot = _textView.TextBuffer.CurrentSnapshot; - for (int i = startLine; i <= endLine; i++) { - var curline = snapshot.GetLineFromLineNumber(i); - edit.Insert(curline.Start, indentation); - } - } - - public int GetExpansionFunction(IXMLDOMNode xmlFunctionNode, string bstrFieldName, out IVsExpansionFunction pFunc) { - pFunc = null; - return VSConstants.S_OK; - } - - public int IsValidKind(IVsTextLines pBuffer, TextSpan[] ts, string bstrKind, out int pfIsValidKind) { - pfIsValidKind = 1; - return VSConstants.S_OK; - } - - public int IsValidType(IVsTextLines pBuffer, TextSpan[] ts, string[] rgTypes, int iCountTypes, out int pfIsValidType) { - pfIsValidType = 1; - return VSConstants.S_OK; - } - - public int OnAfterInsertion(IVsExpansionSession pSession) { - return VSConstants.S_OK; - } - - public int OnBeforeInsertion(IVsExpansionSession pSession) { - _session = pSession; - return VSConstants.S_OK; - } - - public int OnItemChosen(string pszTitle, string pszPath) { - int caretLine, caretColumn; - GetCaretPosition(out caretLine, out caretColumn); - - var textSpan = new TextSpan() { iStartLine = caretLine, iStartIndex = caretColumn, iEndLine = caretLine, iEndIndex = caretColumn }; - return InsertNamedExpansion(pszTitle, pszPath, textSpan); - - } - - public int NextField() { - return _session.GoToNextExpansionField(0); - } - - public int PreviousField() { - return _session.GoToPreviousExpansionField(); - } - - public int EndCurrentExpansion(bool leaveCaret) { - if (_selectEndSpan) { - TextSpan[] endSpan = new TextSpan[1]; - if (ErrorHandler.Succeeded(_session.GetEndSpan(endSpan))) { - var snapshot = _textView.TextBuffer.CurrentSnapshot; - var startLine = snapshot.GetLineFromLineNumber(endSpan[0].iStartLine); - var selectionLength = _selectionEnd.GetPosition(_textView.TextBuffer.CurrentSnapshot) - _selectionStart.GetPosition(_textView.TextBuffer.CurrentSnapshot); - var span = new Span(startLine.Start + endSpan[0].iStartIndex + selectionLength, 0); - _textView.Caret.MoveTo(new SnapshotPoint(snapshot, span.Start)); - return _session.EndCurrentExpansion(1); - } - } - return _session.EndCurrentExpansion(leaveCaret ? 1 : 0); - } - - public int PositionCaretForEditing(IVsTextLines pBuffer, TextSpan[] ts) { - return VSConstants.S_OK; - } - - private void GetCaretPosition(out int caretLine, out int caretColumn) { - ErrorHandler.ThrowOnFailure(_view.GetCaretPos(out caretLine, out caretColumn)); - - // Handle virtual space - int lineLength; - ErrorHandler.ThrowOnFailure(_lines.GetLengthOfLine(caretLine, out lineLength)); - - if (caretColumn > lineLength) { - caretColumn = lineLength; - } - } - - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudioTools; +using MSXML; + +namespace Microsoft.NodejsTools.Intellisense { + internal sealed class ExpansionClient : IVsExpansionClient { + internal IServiceProvider serviceProvider; + + private readonly IVsTextLines _lines; + private readonly IVsExpansion _expansion; + private readonly IVsTextView _view; + private readonly ITextView _textView; + private readonly IVsEditorAdaptersFactoryService _adapterFactory; + private readonly IServiceProvider _serviceProvider; + private IVsExpansionSession _session; + private bool _sessionEnded, _selectEndSpan; + private ITrackingPoint _selectionStart, _selectionEnd; + + public const string SurroundsWith = "SurroundsWith"; + public const string Expansion = "Expansion"; + + public ExpansionClient(ITextView textView, IVsEditorAdaptersFactoryService adapterFactory, IServiceProvider serviceProvider) { + _textView = textView; + _serviceProvider = serviceProvider; + _adapterFactory = adapterFactory; + _view = adapterFactory.GetViewAdapter(textView); + _lines = (IVsTextLines)adapterFactory.GetBufferAdapter(textView.TextBuffer); + _expansion = _lines as IVsExpansion; + if (_expansion == null) { + throw new ArgumentException("TextBuffer does not support expansions"); + } + } + + public bool InSession { + get { + return _session != null; + } + } + + public int EndExpansion() { + _session = null; + _sessionEnded = true; + _selectionStart = _selectionEnd = null; + return VSConstants.S_OK; + } + + public int FormatSpan(IVsTextLines pBuffer, TextSpan[] ts) { + IXMLDOMNode codeNode, snippetTypes, declarations; + int hr; + if (ErrorHandler.Failed(hr = _session.GetSnippetNode("CodeSnippet:Code", out codeNode))) { + return hr; + } + + if (ErrorHandler.Failed(hr = _session.GetHeaderNode("CodeSnippet:SnippetTypes", out snippetTypes))) { + return hr; + } + + List declList = new List(); + if (ErrorHandler.Succeeded(hr = _session.GetSnippetNode("CodeSnippet:Declarations", out declarations)) + && declarations != null) { + foreach (IXMLDOMNode declType in declarations.childNodes) { + var id = declType.selectSingleNode("./CodeSnippet:ID"); + if (id != null) { + declList.Add(id.text); + } + } + } + + bool surroundsWith = false; + foreach (MSXML.IXMLDOMNode snippetType in snippetTypes.childNodes) { + if (snippetType.nodeName == "SnippetType") { + if (snippetType.text == SurroundsWith) { + surroundsWith = true; + } + } + } + // get the indentation of where we're inserting the code... + string baseIndentation = GetBaseIndentation(ts); + + using (var edit = _textView.TextBuffer.CreateEdit()) { + if (surroundsWith) { + var templateText = codeNode.text.Replace("\r\n", VsExtensions.GetNewLineText(_textView.TextSnapshot)); + foreach (var decl in declList) { + string defaultValue; + if (ErrorHandler.Succeeded(_session.GetFieldValue(decl, out defaultValue))) { + templateText = templateText.Replace("$" + decl + "$", defaultValue); + } + } + + templateText = templateText.Replace("$end$", ""); + // we can finally figure out where the selected text began witin the original template... + int selectedIndex = templateText.IndexOf("$selected$"); + if (selectedIndex != -1) { + var selection = _textView.Selection; + + // now we need to get the indentation of the $selected$ element within the template, + // as we'll need to indent the selected code to that level. + string indentation = GetTemplateSelectionIndentation(templateText, selectedIndex); + + var start = _selectionStart.GetPosition(_textView.TextBuffer.CurrentSnapshot); + var end = _selectionEnd.GetPosition(_textView.TextBuffer.CurrentSnapshot); + if (end < start) { + // we didn't actually have a selection, and our negative tracking pushed us + // back to the start of the buffer... + end = start; + } + var selectedSpan = Span.FromBounds(start, end); + + if (surroundsWith) { + if (string.IsNullOrWhiteSpace(_textView.TextBuffer.CurrentSnapshot.GetText(selectedSpan))) { + + // Surround With can be invoked with no selection, but on a line with some text. + // In that case we need to inject an extra new line. + var endLine = _textView.TextBuffer.CurrentSnapshot.GetLineFromPosition(end); + var endText = endLine.GetText().Substring(end - endLine.Start); + if (!String.IsNullOrWhiteSpace(endText)) { + edit.Insert(end, _textView.Options.GetNewLineCharacter()); + } + + } else { + _selectEndSpan = true; + } + + } + + IndentSpan( + edit, + indentation, + _textView.TextBuffer.CurrentSnapshot.GetLineFromPosition(start).LineNumber + 1, // 1st line is already indented + _textView.TextBuffer.CurrentSnapshot.GetLineFromPosition(end).LineNumber + ); + } + } + + // we now need to update any code which was not selected that we just inserted. + IndentSpan(edit, baseIndentation, ts[0].iStartLine + 1, ts[0].iEndLine); + + edit.Apply(); + + } + + return hr; + } + + public int InsertNamedExpansion(string pszTitle, string pszPath, TextSpan textSpan) { + if (_session != null) { + // if the user starts an expansion session while one is in progress + // then abort the current expansion session + _session.EndCurrentExpansion(1); + _session = null; + } + + var selection = _textView.Selection; + var snapshot = selection.Start.Position.Snapshot; + + _selectionStart = snapshot.CreateTrackingPoint(selection.Start.Position, VisualStudio.Text.PointTrackingMode.Positive); + _selectionEnd = snapshot.CreateTrackingPoint(selection.End.Position, VisualStudio.Text.PointTrackingMode.Negative); + _selectEndSpan = _sessionEnded = false; + + int hr = _expansion.InsertNamedExpansion( + pszTitle, + pszPath, + textSpan, + this, + Guids.NodejsLanguageInfo, + 0, + out _session + ); + + if (ErrorHandler.Succeeded(hr)) { + if (_sessionEnded) { + _session = null; + } + } + return hr; + } + + private static string GetTemplateSelectionIndentation(string templateText, int selectedIndex) { + string indentation = ""; + for (int i = selectedIndex - 1; i >= 0; i--) { + if (templateText[i] != '\t' && templateText[i] != ' ') { + indentation = templateText.Substring(i + 1, selectedIndex - i - 1); + break; + } + } + return indentation; + } + + private string GetBaseIndentation(TextSpan[] ts) { + var indentationLine = _textView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(ts[0].iStartLine).GetText(); + string baseIndentation = indentationLine; + for (int i = 0; i < indentationLine.Length; i++) { + if (indentationLine[i] != ' ' && indentationLine[i] != '\t') { + baseIndentation = indentationLine.Substring(0, i); + break; + } + } + return baseIndentation; + } + + private void IndentSpan(ITextEdit edit, string indentation, int startLine, int endLine) { + var snapshot = _textView.TextBuffer.CurrentSnapshot; + for (int i = startLine; i <= endLine; i++) { + var curline = snapshot.GetLineFromLineNumber(i); + edit.Insert(curline.Start, indentation); + } + } + + public int GetExpansionFunction(IXMLDOMNode xmlFunctionNode, string bstrFieldName, out IVsExpansionFunction pFunc) { + pFunc = null; + return VSConstants.S_OK; + } + + public int IsValidKind(IVsTextLines pBuffer, TextSpan[] ts, string bstrKind, out int pfIsValidKind) { + pfIsValidKind = 1; + return VSConstants.S_OK; + } + + public int IsValidType(IVsTextLines pBuffer, TextSpan[] ts, string[] rgTypes, int iCountTypes, out int pfIsValidType) { + pfIsValidType = 1; + return VSConstants.S_OK; + } + + public int OnAfterInsertion(IVsExpansionSession pSession) { + return VSConstants.S_OK; + } + + public int OnBeforeInsertion(IVsExpansionSession pSession) { + _session = pSession; + return VSConstants.S_OK; + } + + public int OnItemChosen(string pszTitle, string pszPath) { + int caretLine, caretColumn; + GetCaretPosition(out caretLine, out caretColumn); + + var textSpan = new TextSpan() { iStartLine = caretLine, iStartIndex = caretColumn, iEndLine = caretLine, iEndIndex = caretColumn }; + return InsertNamedExpansion(pszTitle, pszPath, textSpan); + + } + + public int NextField() { + return _session.GoToNextExpansionField(0); + } + + public int PreviousField() { + return _session.GoToPreviousExpansionField(); + } + + public int EndCurrentExpansion(bool leaveCaret) { + if (_selectEndSpan) { + TextSpan[] endSpan = new TextSpan[1]; + if (ErrorHandler.Succeeded(_session.GetEndSpan(endSpan))) { + var snapshot = _textView.TextBuffer.CurrentSnapshot; + var startLine = snapshot.GetLineFromLineNumber(endSpan[0].iStartLine); + var selectionLength = _selectionEnd.GetPosition(_textView.TextBuffer.CurrentSnapshot) - _selectionStart.GetPosition(_textView.TextBuffer.CurrentSnapshot); + var span = new Span(startLine.Start + endSpan[0].iStartIndex + selectionLength, 0); + _textView.Caret.MoveTo(new SnapshotPoint(snapshot, span.Start)); + return _session.EndCurrentExpansion(1); + } + } + return _session.EndCurrentExpansion(leaveCaret ? 1 : 0); + } + + public int PositionCaretForEditing(IVsTextLines pBuffer, TextSpan[] ts) { + return VSConstants.S_OK; + } + + private void GetCaretPosition(out int caretLine, out int caretColumn) { + ErrorHandler.ThrowOnFailure(_view.GetCaretPos(out caretLine, out caretColumn)); + + // Handle virtual space + int lineLength; + ErrorHandler.ThrowOnFailure(_lines.GetLengthOfLine(caretLine, out lineLength)); + + if (caretColumn > lineLength) { + caretColumn = lineLength; + } + } + + } } \ No newline at end of file diff --git a/Nodejs/Product/Nodejs/Intellisense/IntellisenseController.cs b/Nodejs/Product/Nodejs/Intellisense/IntellisenseController.cs index 3e8e93289..7f925b5dc 100644 --- a/Nodejs/Product/Nodejs/Intellisense/IntellisenseController.cs +++ b/Nodejs/Product/Nodejs/Intellisense/IntellisenseController.cs @@ -1,783 +1,783 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Diagnostics; -using System.Linq; -using System.Windows.Input; -using Microsoft.NodejsTools.Classifier; -using Microsoft.NodejsTools.Editor.Core; -using Microsoft.NodejsTools.Project; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Editor; -using Microsoft.VisualStudio.Language.Intellisense; -using Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Classification; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.IncrementalSearch; -using Microsoft.VisualStudio.Text.Operations; -using Microsoft.VisualStudio.TextManager.Interop; -using VSConstants = Microsoft.VisualStudio.VSConstants; - -namespace Microsoft.NodejsTools.Intellisense { - - internal sealed class IntellisenseController : IIntellisenseController, IOleCommandTarget { - private readonly ITextView _textView; - private readonly IntellisenseControllerProvider _provider; - private readonly IIncrementalSearch _incSearch; - private readonly ExpansionClient _expansionClient; - private readonly System.IServiceProvider _serviceProvider; - private readonly IVsExpansionManager _expansionMgr; - private readonly IClassifier _classifier; - private ICompletionSession _activeSession; - private ISignatureHelpSession _sigHelpSession; - private IQuickInfoSession _quickInfoSession; - private IOleCommandTarget _oldTarget; - private IEditorOperations _editOps; - private static string[] _allStandardSnippetTypes = { ExpansionClient.Expansion, ExpansionClient.SurroundsWith }; - private static string[] _surroundsWithSnippetTypes = { ExpansionClient.SurroundsWith }; - [ThreadStatic] - internal static bool ForceCompletions; - - /// - /// Attaches events for invoking Statement completion - /// - public IntellisenseController(IntellisenseControllerProvider provider, ITextView textView, System.IServiceProvider serviceProvider) { - _textView = textView; - _provider = provider; - _classifier = _provider._classifierAgg.GetClassifier(_textView.TextBuffer); - _editOps = provider._EditOperationsFactory.GetEditorOperations(textView); - _incSearch = provider._IncrementalSearch.GetIncrementalSearch(textView); - _textView.MouseHover += TextViewMouseHover; - _serviceProvider = serviceProvider; - - if (textView.TextBuffer.IsNodeJsContent()) { - try { - _expansionClient = new ExpansionClient(textView, provider._adaptersFactory, _serviceProvider); - var textMgr = (IVsTextManager2)_serviceProvider.GetService(typeof(SVsTextManager)); - textMgr.GetExpansionManager(out _expansionMgr); - } catch (ArgumentException ex) { - // No expansion client for this buffer, but we can continue without it - Debug.Fail(ex.ToString()); - } - } - textView.Properties.AddProperty(typeof(IntellisenseController), this); // added so our key processors can get back to us - } - - private void TextViewMouseHover(object sender, MouseHoverEventArgs e) { - if (_quickInfoSession != null && !_quickInfoSession.IsDismissed) { - _quickInfoSession.Dismiss(); - } - var pt = e.TextPosition.GetPoint(EditorExtensions.IsNodeJsContent, PositionAffinity.Successor); - if (pt != null) { - _quickInfoSession = _provider._QuickInfoBroker.TriggerQuickInfo( - _textView, - pt.Value.Snapshot.CreateTrackingPoint(pt.Value.Position, PointTrackingMode.Positive), - true); - } - } - - internal void TriggerQuickInfo() { - if (_quickInfoSession != null && !_quickInfoSession.IsDismissed) { - _quickInfoSession.Dismiss(); - } - _quickInfoSession = _provider._QuickInfoBroker.TriggerQuickInfo(_textView); - } - - public void ConnectSubjectBuffer(ITextBuffer subjectBuffer) { - subjectBuffer.GetAnalyzer().AddBuffer(subjectBuffer); - } - - public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer) { - subjectBuffer.GetAnalyzer().RemoveBuffer(subjectBuffer); - } - - /// - /// Detaches the events - /// - /// - public void Detach(ITextView textView) { - if (_textView == null) { - throw new InvalidOperationException("Already detached from text view"); - } - if (textView != _textView) { - throw new ArgumentException("Not attached to specified text view", "textView"); - } - - _textView.MouseHover -= TextViewMouseHover; - _textView.Properties.RemoveProperty(typeof(IntellisenseController)); - - DetachKeyboardFilter(); - } - - /// - /// Triggers Statement completion when appropriate keys are pressed - /// The key combination is CTRL-J or "." - /// The intellisense window is dismissed when one presses ESC key - /// - /// - /// - private void OnPreprocessKeyDown(object sender, TextCompositionEventArgs e) { - // We should only receive pre-process events from our text view - Debug.Assert(sender == _textView); - - // TODO: We should handle = for signature completion of keyword arguments - - string text = e.Text; - if (text.Length == 1) { - HandleChar(text[0]); - } - } - - private void HandleChar(char ch) { - // We trigger completions when the user types . or space. Called via our IOleCommandTarget filter - // on the text view. - // - // We trigger signature help when we receive a "(". We update our current sig when - // we receive a "," and we close sig help when we receive a ")". - - if (!_incSearch.IsActive) { - switch (ch) { - case '.': - case ' ': - if (NodejsPackage.Instance.LangPrefs.AutoListMembers) { - TriggerCompletionSession(false); - } - break; - case '/': - case '\'': - case '"': - if (CompletionSource.ShouldTriggerRequireIntellisense(_textView.Caret.Position.BufferPosition, _classifier, true, true)) { - TriggerCompletionSession(false); - } - break; - case '(': - if (CompletionSource.ShouldTriggerRequireIntellisense(_textView.Caret.Position.BufferPosition, _classifier, true)) { - TriggerCompletionSession(false); - } else if (NodejsPackage.Instance.LangPrefs.AutoListParams) { - OpenParenStartSignatureSession(); - } - break; - case ')': - if (_sigHelpSession != null) { - _sigHelpSession.Dismiss(); - _sigHelpSession = null; - } - - if (NodejsPackage.Instance.LangPrefs.AutoListParams) { - // trigger help for outer call if there is one - TriggerSignatureHelp(); - } - break; - case '=': - case ',': - if (_sigHelpSession == null) { - if (NodejsPackage.Instance.LangPrefs.AutoListParams) { - CommaStartSignatureSession(); - } - } else { - UpdateCurrentParameter(); - } - break; - default: - if (IsIdentifierFirstChar(ch) && _activeSession == null - && NodejsPackage.Instance.LangPrefs.AutoListMembers - && NodejsPackage.Instance.IntellisenseOptionsPage.ShowCompletionListAfterCharacterTyped) { - TriggerCompletionSession(false); - } - break; - } - } - } - - private bool Backspace() { - if (_sigHelpSession != null) { - if (_textView.Selection.IsActive && !_textView.Selection.IsEmpty) { - // when deleting a selection don't do anything to pop up signature help again - _sigHelpSession.Dismiss(); - return false; - } - - SnapshotPoint? caretPoint = _textView.BufferGraph.MapDownToFirstMatch( - _textView.Caret.Position.BufferPosition, - PointTrackingMode.Positive, - EditorExtensions.IsNodeJsContent, - PositionAffinity.Predecessor - ); - - if (caretPoint != null && caretPoint.Value.Position != 0) { - var deleting = caretPoint.Value.Snapshot[caretPoint.Value.Position - 1]; - if (deleting == ',') { - caretPoint.Value.Snapshot.TextBuffer.Delete(new Span(caretPoint.Value.Position - 1, 1)); - UpdateCurrentParameter(); - return true; - } else if (deleting == '(' || deleting == ')') { - _sigHelpSession.Dismiss(); - // delete the ( before triggering help again - caretPoint.Value.Snapshot.TextBuffer.Delete(new Span(caretPoint.Value.Position - 1, 1)); - - // Pop to an outer nesting of signature help - if (NodejsPackage.Instance.LangPrefs.AutoListParams) { - TriggerSignatureHelp(); - } - - return true; - } - } - } - return false; - } - - private void OpenParenStartSignatureSession() { - if (_activeSession != null) { - _activeSession.Dismiss(); - } - if (_sigHelpSession != null) { - _sigHelpSession.Dismiss(); - } - - TriggerSignatureHelp(); - } - - private void CommaStartSignatureSession() { - TriggerSignatureHelp(); - } - - /// - /// Updates the current parameter for the caret's current position. - /// - /// This will analyze the buffer for where we are currently located, find the current - /// parameter that we're entering, and then update the signature. If our current - /// signature does not have enough parameters we'll find a signature which does. - /// - private void UpdateCurrentParameter() { - if (_sigHelpSession == null) { - // we moved out of the original span for sig help, re-trigger based upon the position - TriggerSignatureHelp(); - return; - } - - int position = _textView.Caret.Position.BufferPosition.Position; - // we advance to the next parameter - // TODO: need to parse and see if we have keyword arguments entered into the current signature yet - NodejsSignature sig = _sigHelpSession.SelectedSignature as NodejsSignature; - if (sig != null) { - var prevBuffer = sig.ApplicableToSpan.TextBuffer; - var textBuffer = _textView.TextBuffer; - - var targetPt = _textView.BufferGraph.MapDownToFirstMatch( - new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, position), - PointTrackingMode.Positive, - EditorExtensions.IsNodeJsContent, - PositionAffinity.Successor - ); - - if (targetPt != null) { - var span = targetPt.Value.Snapshot.CreateTrackingSpan(targetPt.Value.Position, 0, SpanTrackingMode.EdgeInclusive); - - var sigs = VsProjectAnalyzer.GetSignatures(targetPt.Value.Snapshot, span); - bool retrigger = false; - if (sigs.Signatures.Count == _sigHelpSession.Signatures.Count) { - for (int i = 0; i < sigs.Signatures.Count && !retrigger; i++) { - var leftSig = sigs.Signatures[i]; - var rightSig = _sigHelpSession.Signatures[i]; - - if (leftSig.Parameters.Count == rightSig.Parameters.Count) { - for (int j = 0; j < leftSig.Parameters.Count; j++) { - var leftParam = leftSig.Parameters[j]; - var rightParam = rightSig.Parameters[j]; - - if (leftParam.Name != rightParam.Name || leftParam.Documentation != rightParam.Documentation) { - retrigger = true; - break; - } - } - } - - if (leftSig.Content != rightSig.Content || leftSig.Documentation != rightSig.Documentation) { - retrigger = true; - } - } - } else { - retrigger = true; - } - - if (retrigger) { - _sigHelpSession.Dismiss(); - TriggerSignatureHelp(); - } else { - int curParam = sigs.ParameterIndex; - if (sigs.LastKeywordArgument != null) { - curParam = Int32.MaxValue; - for (int i = 0; i < sig.Parameters.Count; i++) { - if (sig.Parameters[i].Name == sigs.LastKeywordArgument) { - curParam = i; - break; - } - } - } - - if (curParam < sig.Parameters.Count) { - sig.SetCurrentParameter(sig.Parameters[curParam]); - } else if (sigs.LastKeywordArgument == String.Empty) { - sig.SetCurrentParameter(null); - } else { - CommaFindBestSignature(curParam, sigs.LastKeywordArgument); - } - } - } - } - } - - private void CommaFindBestSignature(int curParam, string lastKeywordArg) { - // see if we have a signature which accomodates this... - - // TODO: We should also take into account param arrays - // TODO: We should also get the types of the arguments and use that to - // pick the best signature when the signature includes types. - foreach (var availableSig in _sigHelpSession.Signatures) { - if (lastKeywordArg != null) { - for (int i = 0; i < availableSig.Parameters.Count; i++) { - if (availableSig.Parameters[i].Name == lastKeywordArg) { - _sigHelpSession.SelectedSignature = availableSig; - - NodejsSignature sig = availableSig as NodejsSignature; - if (sig != null) { - sig.SetCurrentParameter(sig.Parameters[i]); - } - break; - } - } - } else if (availableSig.Parameters.Count > curParam) { - _sigHelpSession.SelectedSignature = availableSig; - - NodejsSignature sig = availableSig as NodejsSignature; - if (sig != null) { - sig.SetCurrentParameter(sig.Parameters[curParam]); - } - break; - } - } - } - - internal void TriggerCompletionSession(bool completeWord) { - Dismiss(); - - _activeSession = CompletionBroker.TriggerCompletion(_textView); - - if (_activeSession != null) { - FuzzyCompletionSet set; - if (completeWord && - _activeSession.CompletionSets.Count == 1 && - (set = _activeSession.CompletionSets[0] as FuzzyCompletionSet) != null && - set.SelectSingleBest()) { - _activeSession.Commit(); - _activeSession = null; - } else { - _activeSession.Filter(); - _activeSession.Dismissed += OnCompletionSessionDismissedOrCommitted; - _activeSession.Committed += OnCompletionSessionDismissedOrCommitted; - } - } - } - - internal void TriggerSignatureHelp() { - if (_sigHelpSession != null) { - _sigHelpSession.Dismiss(); - } - - _sigHelpSession = SignatureBroker.TriggerSignatureHelp(_textView); - - if (_sigHelpSession != null) { - _sigHelpSession.Dismissed += OnSignatureSessionDismissed; - ISignature sig; - if (_sigHelpSession.Properties.TryGetProperty(typeof(NodejsSignature), out sig)) { - _sigHelpSession.SelectedSignature = sig; - - IParameter param; - if (_sigHelpSession.Properties.TryGetProperty(typeof(NodejsParameter), out param)) { - ((NodejsSignature)sig).SetCurrentParameter(param); - } - } - } - } - - private void OnCompletionSessionDismissedOrCommitted(object sender, System.EventArgs e) { - // We've just been told that our active session was dismissed. We should remove all references to it. - _activeSession.Committed -= OnCompletionSessionDismissedOrCommitted; - _activeSession.Dismissed -= OnCompletionSessionDismissedOrCommitted; - _activeSession = null; - } - - private void OnSignatureSessionDismissed(object sender, System.EventArgs e) { - // We've just been told that our active session was dismissed. We should remove all references to it. - _sigHelpSession.Dismissed -= OnSignatureSessionDismissed; - _sigHelpSession = null; - } - - private void DeleteSelectedSpans() { - if (!_textView.Selection.IsEmpty) { - _editOps.Delete(); - } - } - - private void Dismiss() { - if (_activeSession != null) { - _activeSession.Dismiss(); - } - } - - internal ICompletionBroker CompletionBroker { - get { - return _provider._CompletionBroker; - } - } - - internal IVsEditorAdaptersFactoryService AdaptersFactory { - get { - return _provider._adaptersFactory; - } - } - - internal ISignatureHelpBroker SignatureBroker { - get { - return _provider._SigBroker; - } - } - - #region IOleCommandTarget Members - - // we need this because VS won't give us certain keyboard events as they're handled before our key processor. These - // include enter and tab both of which we want to complete. - - internal void AttachKeyboardFilter() { - if (_oldTarget == null) { - var viewAdapter = AdaptersFactory.GetViewAdapter(_textView); - if (viewAdapter != null) { - ErrorHandler.ThrowOnFailure(viewAdapter.AddCommandFilter(this, out _oldTarget)); - } - } - } - - private void DetachKeyboardFilter() { - if (_oldTarget != null) { - ErrorHandler.ThrowOnFailure(AdaptersFactory.GetViewAdapter(_textView).RemoveCommandFilter(this)); - _oldTarget = null; - } - } - - private IVsTextView GetViewAdapter() { - return _provider._adaptersFactory.GetViewAdapter(_textView); - } - - public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { - if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (int)VSConstants.VSStd2KCmdID.TYPECHAR) { - var ch = (char)(ushort)System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant(pvaIn); - - if (_activeSession != null && !_activeSession.IsDismissed) { - if (_activeSession.SelectedCompletionSet.SelectionStatus.IsSelected) { - var completion = _activeSession.SelectedCompletionSet.SelectionStatus.Completion; - - string committedBy = String.Empty; - if (_activeSession.SelectedCompletionSet.Moniker == CompletionSource.NodejsRequireCompletionSetMoniker) { - if (completion.InsertionText.StartsWith("'")) { // require( - committedBy = ")"; - } else if (completion.InsertionText.EndsWith("'")) { // require(' - committedBy = "'"; - } else if (completion.InsertionText.EndsWith("\"")) { // require(" - committedBy = "\""; - } - } else { - committedBy = NodejsPackage.Instance != null && NodejsPackage.Instance.IntellisenseOptionsPage.OnlyTabOrEnterToCommit ? - string.Empty : - NodejsConstants.DefaultIntellisenseCompletionCommittedBy; - } - - if (committedBy.IndexOf(ch) != -1) { - _activeSession.Commit(); - if ((completion.InsertionText.EndsWith("'") && ch == '\'') || - (completion.InsertionText.EndsWith("\"") && ch == '"')) { - // https://nodejstools.codeplex.com/workitem/960 - // ' triggers the completion, but we don't want to insert the quote. - return VSConstants.S_OK; - } - } - } else if (_activeSession.SelectedCompletionSet.Moniker.Equals(CompletionSource.NodejsRequireCompletionSetMoniker) && !IsRequireIdentifierChar(ch)) { - _activeSession.Dismiss(); - } else if (!_activeSession.SelectedCompletionSet.Moniker.Equals(CompletionSource.NodejsRequireCompletionSetMoniker) && !IsIdentifierChar(ch)) { - _activeSession.Dismiss(); - } - } - - int res = _oldTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); - - if (_activeSession == null || !_activeSession.SelectedCompletionSet.Moniker.Equals(CompletionSource.NodejsRequireCompletionSetMoniker)) { - //Only process the char if we are not in a require completion - HandleChar((char)(ushort)System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant(pvaIn)); - } - - if (_activeSession != null && !_activeSession.IsDismissed) { - _activeSession.Filter(); - } - - return res; - } - - if (_activeSession != null) { - if (pguidCmdGroup == VSConstants.VSStd2K) { - switch ((VSConstants.VSStd2KCmdID)nCmdID) { - case VSConstants.VSStd2KCmdID.RETURN: - if (/*NodejsPackage.Instance.AdvancedEditorOptionsPage.EnterCommitsIntellisense*/ true && - !_activeSession.IsDismissed && - _activeSession.SelectedCompletionSet.SelectionStatus.IsSelected) { - - // If the user has typed all of the characters as the completion and presses - // enter we should dismiss & let the text editor receive the enter. For example - // when typing "import sys[ENTER]" completion starts after the space. After typing - // sys the user wants a new line and doesn't want to type enter twice. - - bool enterOnComplete = /*NodejsPackage.Instance.AdvancedEditorOptionsPage.AddNewLineAtEndOfFullyTypedWord*/true && - EnterOnCompleteText(); - - _activeSession.Commit(); - - if (!enterOnComplete) { - return VSConstants.S_OK; - } - } else { - _activeSession.Dismiss(); - } - break; - case VSConstants.VSStd2KCmdID.TAB: - if (!_activeSession.IsDismissed) { - _activeSession.Commit(); - return VSConstants.S_OK; - } - break; - case VSConstants.VSStd2KCmdID.BACKSPACE: - case VSConstants.VSStd2KCmdID.DELETE: - case VSConstants.VSStd2KCmdID.DELETEWORDLEFT: - case VSConstants.VSStd2KCmdID.DELETEWORDRIGHT: - int res = _oldTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); - if (_activeSession != null && !_activeSession.IsDismissed) { - _activeSession.Filter(); - } - return res; - } - } - } else if (_sigHelpSession != null) { - if (pguidCmdGroup == VSConstants.VSStd2K) { - switch ((VSConstants.VSStd2KCmdID)nCmdID) { - case VSConstants.VSStd2KCmdID.BACKSPACE: - bool fDeleted = Backspace(); - if (fDeleted) { - return VSConstants.S_OK; - } - break; - case VSConstants.VSStd2KCmdID.LEFT: - _editOps.MoveToPreviousCharacter(false); - UpdateCurrentParameter(); - return VSConstants.S_OK; - case VSConstants.VSStd2KCmdID.RIGHT: - _editOps.MoveToNextCharacter(false); - UpdateCurrentParameter(); - return VSConstants.S_OK; - case VSConstants.VSStd2KCmdID.HOME: - case VSConstants.VSStd2KCmdID.BOL: - case VSConstants.VSStd2KCmdID.BOL_EXT: - case VSConstants.VSStd2KCmdID.EOL: - case VSConstants.VSStd2KCmdID.EOL_EXT: - case VSConstants.VSStd2KCmdID.END: - case VSConstants.VSStd2KCmdID.WORDPREV: - case VSConstants.VSStd2KCmdID.WORDPREV_EXT: - case VSConstants.VSStd2KCmdID.DELETEWORDLEFT: - _sigHelpSession.Dismiss(); - _sigHelpSession = null; - break; - } - } - } - if (pguidCmdGroup == VSConstants.VSStd2K) { - switch ((VSConstants.VSStd2KCmdID)nCmdID) { - case VSConstants.VSStd2KCmdID.QUICKINFO: - TriggerQuickInfo(); - return VSConstants.S_OK; - case VSConstants.VSStd2KCmdID.PARAMINFO: - TriggerSignatureHelp(); - return VSConstants.S_OK; - case VSConstants.VSStd2KCmdID.RETURN: - if (_expansionMgr != null && _expansionClient.InSession && ErrorHandler.Succeeded(_expansionClient.EndCurrentExpansion(false))) { - return VSConstants.S_OK; - } - break; - case VSConstants.VSStd2KCmdID.TAB: - if (_expansionMgr != null && _expansionClient.InSession && ErrorHandler.Succeeded(_expansionClient.NextField())) { - return VSConstants.S_OK; - } - if (_textView.Selection.IsEmpty && _textView.Caret.Position.BufferPosition > 0) { - if (TryTriggerExpansion()) { - return VSConstants.S_OK; - } - } - break; - case VSConstants.VSStd2KCmdID.BACKTAB: - if (_expansionMgr != null && _expansionClient.InSession && ErrorHandler.Succeeded(_expansionClient.PreviousField())) { - return VSConstants.S_OK; - } - break; - case VSConstants.VSStd2KCmdID.SURROUNDWITH: - case VSConstants.VSStd2KCmdID.INSERTSNIPPET: - TriggerSnippet(nCmdID); - return VSConstants.S_OK; - case VSConstants.VSStd2KCmdID.SHOWMEMBERLIST: - case VSConstants.VSStd2KCmdID.COMPLETEWORD: - ForceCompletions = true; - try { - TriggerCompletionSession((VSConstants.VSStd2KCmdID)nCmdID == VSConstants.VSStd2KCmdID.COMPLETEWORD - && !NodejsPackage.Instance.IntellisenseOptionsPage.OnlyTabOrEnterToCommit); - } finally { - ForceCompletions = false; - } - return VSConstants.S_OK; - } - } - return _oldTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); - } - - private void TriggerSnippet(uint nCmdID) { - if (_expansionMgr != null) { - string prompt; - string[] snippetTypes; - if ((VSConstants.VSStd2KCmdID)nCmdID == VSConstants.VSStd2KCmdID.SURROUNDWITH) { - prompt = SR.GetString(SR.SurroundWith); - snippetTypes = _surroundsWithSnippetTypes; - } else { - prompt = SR.GetString(SR.InsertSnippet); - snippetTypes = _allStandardSnippetTypes; - } - - _expansionMgr.InvokeInsertionUI( - GetViewAdapter(), - _expansionClient, - Guids.NodejsLanguageInfo, - snippetTypes, - snippetTypes.Length, - 0, - null, - 0, - 0, - prompt, - ">" - ); - } - } - - private bool TryTriggerExpansion() { - if (_expansionMgr != null) { - var snapshot = _textView.TextBuffer.CurrentSnapshot; - var span = new SnapshotSpan(snapshot, new Span(_textView.Caret.Position.BufferPosition.Position - 1, 1)); - var classification = _textView.TextBuffer.GetNodejsClassifier().GetClassificationSpans(span); - if (classification.Count == 1) { - var clsSpan = classification.First().Span; - var text = classification.First().Span.GetText(); - - TextSpan[] textSpan = new TextSpan[1]; - textSpan[0].iStartLine = clsSpan.Start.GetContainingLine().LineNumber; - textSpan[0].iStartIndex = clsSpan.Start.Position - clsSpan.Start.GetContainingLine().Start; - textSpan[0].iEndLine = clsSpan.End.GetContainingLine().LineNumber; - textSpan[0].iEndIndex = clsSpan.End.Position - clsSpan.End.GetContainingLine().Start; - - string expansionPath, title; - int hr = _expansionMgr.GetExpansionByShortcut( - _expansionClient, - Guids.NodejsLanguageInfo, - text, - GetViewAdapter(), - textSpan, - 1, - out expansionPath, - out title - ); - if (ErrorHandler.Succeeded(hr)) { - // hr may be S_FALSE if there are multiple expansions, - // so we don't want to InsertNamedExpansion yet. VS will - // pop up a selection dialog in this case. - if (hr == VSConstants.S_OK) { - return ErrorHandler.Succeeded(_expansionClient.InsertNamedExpansion(title, expansionPath, textSpan[0])); - } - return true; - } - } - } - return false; - } - - private static bool IsRequireIdentifierChar(char ch) { - return ch == '_' - || ch == '.' - || ch == '/' - || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'); - } - - private static bool IsIdentifierFirstChar(char ch) { - return ch == '_' || ch == '$'|| (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); - } - - private static bool IsIdentifierChar(char ch) { - return IsIdentifierFirstChar(ch) || (ch >= '0' && ch <= '9'); - } - - private bool EnterOnCompleteText() { - SnapshotPoint? point = _activeSession.GetTriggerPoint(_textView.TextBuffer.CurrentSnapshot); - if (point.HasValue) { - int chars = _textView.Caret.Position.BufferPosition.Position - point.Value.Position; - var selectionStatus = _activeSession.SelectedCompletionSet.SelectionStatus; - if (chars == selectionStatus.Completion.InsertionText.Length) { - string text = _textView.TextSnapshot.GetText(point.Value.Position, chars); - - if (String.Compare(text, selectionStatus.Completion.InsertionText, true) == 0) { - return true; - } - } - } - - return false; - } - - public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { - if (pguidCmdGroup == VSConstants.VSStd2K) { - for (int i = 0; i < cCmds; i++) { - switch ((VSConstants.VSStd2KCmdID)prgCmds[i].cmdID) { - case VSConstants.VSStd2KCmdID.SHOWMEMBERLIST: - case VSConstants.VSStd2KCmdID.COMPLETEWORD: - case VSConstants.VSStd2KCmdID.QUICKINFO: - case VSConstants.VSStd2KCmdID.PARAMINFO: - case VSConstants.VSStd2KCmdID.SURROUNDWITH: - case VSConstants.VSStd2KCmdID.INSERTSNIPPET: - prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED); - return VSConstants.S_OK; - } - } - } - - return _oldTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); - } - - #endregion - } -} - +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Diagnostics; +using System.Linq; +using System.Windows.Input; +using Microsoft.NodejsTools.Classifier; +using Microsoft.NodejsTools.Editor.Core; +using Microsoft.NodejsTools.Project; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Classification; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.IncrementalSearch; +using Microsoft.VisualStudio.Text.Operations; +using Microsoft.VisualStudio.TextManager.Interop; +using VSConstants = Microsoft.VisualStudio.VSConstants; + +namespace Microsoft.NodejsTools.Intellisense { + + internal sealed class IntellisenseController : IIntellisenseController, IOleCommandTarget { + private readonly ITextView _textView; + private readonly IntellisenseControllerProvider _provider; + private readonly IIncrementalSearch _incSearch; + private readonly ExpansionClient _expansionClient; + private readonly System.IServiceProvider _serviceProvider; + private readonly IVsExpansionManager _expansionMgr; + private readonly IClassifier _classifier; + private ICompletionSession _activeSession; + private ISignatureHelpSession _sigHelpSession; + private IQuickInfoSession _quickInfoSession; + private IOleCommandTarget _oldTarget; + private IEditorOperations _editOps; + private static string[] _allStandardSnippetTypes = { ExpansionClient.Expansion, ExpansionClient.SurroundsWith }; + private static string[] _surroundsWithSnippetTypes = { ExpansionClient.SurroundsWith }; + [ThreadStatic] + internal static bool ForceCompletions; + + /// + /// Attaches events for invoking Statement completion + /// + public IntellisenseController(IntellisenseControllerProvider provider, ITextView textView, System.IServiceProvider serviceProvider) { + _textView = textView; + _provider = provider; + _classifier = _provider._classifierAgg.GetClassifier(_textView.TextBuffer); + _editOps = provider._EditOperationsFactory.GetEditorOperations(textView); + _incSearch = provider._IncrementalSearch.GetIncrementalSearch(textView); + _textView.MouseHover += TextViewMouseHover; + _serviceProvider = serviceProvider; + + if (textView.TextBuffer.IsNodeJsContent()) { + try { + _expansionClient = new ExpansionClient(textView, provider._adaptersFactory, _serviceProvider); + var textMgr = (IVsTextManager2)_serviceProvider.GetService(typeof(SVsTextManager)); + textMgr.GetExpansionManager(out _expansionMgr); + } catch (ArgumentException ex) { + // No expansion client for this buffer, but we can continue without it + Debug.Fail(ex.ToString()); + } + } + textView.Properties.AddProperty(typeof(IntellisenseController), this); // added so our key processors can get back to us + } + + private void TextViewMouseHover(object sender, MouseHoverEventArgs e) { + if (_quickInfoSession != null && !_quickInfoSession.IsDismissed) { + _quickInfoSession.Dismiss(); + } + var pt = e.TextPosition.GetPoint(EditorExtensions.IsNodeJsContent, PositionAffinity.Successor); + if (pt != null) { + _quickInfoSession = _provider._QuickInfoBroker.TriggerQuickInfo( + _textView, + pt.Value.Snapshot.CreateTrackingPoint(pt.Value.Position, PointTrackingMode.Positive), + true); + } + } + + internal void TriggerQuickInfo() { + if (_quickInfoSession != null && !_quickInfoSession.IsDismissed) { + _quickInfoSession.Dismiss(); + } + _quickInfoSession = _provider._QuickInfoBroker.TriggerQuickInfo(_textView); + } + + public void ConnectSubjectBuffer(ITextBuffer subjectBuffer) { + subjectBuffer.GetAnalyzer().AddBuffer(subjectBuffer); + } + + public void DisconnectSubjectBuffer(ITextBuffer subjectBuffer) { + subjectBuffer.GetAnalyzer().RemoveBuffer(subjectBuffer); + } + + /// + /// Detaches the events + /// + /// + public void Detach(ITextView textView) { + if (_textView == null) { + throw new InvalidOperationException("Already detached from text view"); + } + if (textView != _textView) { + throw new ArgumentException("Not attached to specified text view", "textView"); + } + + _textView.MouseHover -= TextViewMouseHover; + _textView.Properties.RemoveProperty(typeof(IntellisenseController)); + + DetachKeyboardFilter(); + } + + /// + /// Triggers Statement completion when appropriate keys are pressed + /// The key combination is CTRL-J or "." + /// The intellisense window is dismissed when one presses ESC key + /// + /// + /// + private void OnPreprocessKeyDown(object sender, TextCompositionEventArgs e) { + // We should only receive pre-process events from our text view + Debug.Assert(sender == _textView); + + // TODO: We should handle = for signature completion of keyword arguments + + string text = e.Text; + if (text.Length == 1) { + HandleChar(text[0]); + } + } + + private void HandleChar(char ch) { + // We trigger completions when the user types . or space. Called via our IOleCommandTarget filter + // on the text view. + // + // We trigger signature help when we receive a "(". We update our current sig when + // we receive a "," and we close sig help when we receive a ")". + + if (!_incSearch.IsActive) { + switch (ch) { + case '.': + case ' ': + if (NodejsPackage.Instance.LangPrefs.AutoListMembers) { + TriggerCompletionSession(false); + } + break; + case '/': + case '\'': + case '"': + if (CompletionSource.ShouldTriggerRequireIntellisense(_textView.Caret.Position.BufferPosition, _classifier, true, true)) { + TriggerCompletionSession(false); + } + break; + case '(': + if (CompletionSource.ShouldTriggerRequireIntellisense(_textView.Caret.Position.BufferPosition, _classifier, true)) { + TriggerCompletionSession(false); + } else if (NodejsPackage.Instance.LangPrefs.AutoListParams) { + OpenParenStartSignatureSession(); + } + break; + case ')': + if (_sigHelpSession != null) { + _sigHelpSession.Dismiss(); + _sigHelpSession = null; + } + + if (NodejsPackage.Instance.LangPrefs.AutoListParams) { + // trigger help for outer call if there is one + TriggerSignatureHelp(); + } + break; + case '=': + case ',': + if (_sigHelpSession == null) { + if (NodejsPackage.Instance.LangPrefs.AutoListParams) { + CommaStartSignatureSession(); + } + } else { + UpdateCurrentParameter(); + } + break; + default: + if (IsIdentifierFirstChar(ch) && _activeSession == null + && NodejsPackage.Instance.LangPrefs.AutoListMembers + && NodejsPackage.Instance.IntellisenseOptionsPage.ShowCompletionListAfterCharacterTyped) { + TriggerCompletionSession(false); + } + break; + } + } + } + + private bool Backspace() { + if (_sigHelpSession != null) { + if (_textView.Selection.IsActive && !_textView.Selection.IsEmpty) { + // when deleting a selection don't do anything to pop up signature help again + _sigHelpSession.Dismiss(); + return false; + } + + SnapshotPoint? caretPoint = _textView.BufferGraph.MapDownToFirstMatch( + _textView.Caret.Position.BufferPosition, + PointTrackingMode.Positive, + EditorExtensions.IsNodeJsContent, + PositionAffinity.Predecessor + ); + + if (caretPoint != null && caretPoint.Value.Position != 0) { + var deleting = caretPoint.Value.Snapshot[caretPoint.Value.Position - 1]; + if (deleting == ',') { + caretPoint.Value.Snapshot.TextBuffer.Delete(new Span(caretPoint.Value.Position - 1, 1)); + UpdateCurrentParameter(); + return true; + } else if (deleting == '(' || deleting == ')') { + _sigHelpSession.Dismiss(); + // delete the ( before triggering help again + caretPoint.Value.Snapshot.TextBuffer.Delete(new Span(caretPoint.Value.Position - 1, 1)); + + // Pop to an outer nesting of signature help + if (NodejsPackage.Instance.LangPrefs.AutoListParams) { + TriggerSignatureHelp(); + } + + return true; + } + } + } + return false; + } + + private void OpenParenStartSignatureSession() { + if (_activeSession != null) { + _activeSession.Dismiss(); + } + if (_sigHelpSession != null) { + _sigHelpSession.Dismiss(); + } + + TriggerSignatureHelp(); + } + + private void CommaStartSignatureSession() { + TriggerSignatureHelp(); + } + + /// + /// Updates the current parameter for the caret's current position. + /// + /// This will analyze the buffer for where we are currently located, find the current + /// parameter that we're entering, and then update the signature. If our current + /// signature does not have enough parameters we'll find a signature which does. + /// + private void UpdateCurrentParameter() { + if (_sigHelpSession == null) { + // we moved out of the original span for sig help, re-trigger based upon the position + TriggerSignatureHelp(); + return; + } + + int position = _textView.Caret.Position.BufferPosition.Position; + // we advance to the next parameter + // TODO: need to parse and see if we have keyword arguments entered into the current signature yet + NodejsSignature sig = _sigHelpSession.SelectedSignature as NodejsSignature; + if (sig != null) { + var prevBuffer = sig.ApplicableToSpan.TextBuffer; + var textBuffer = _textView.TextBuffer; + + var targetPt = _textView.BufferGraph.MapDownToFirstMatch( + new SnapshotPoint(_textView.TextBuffer.CurrentSnapshot, position), + PointTrackingMode.Positive, + EditorExtensions.IsNodeJsContent, + PositionAffinity.Successor + ); + + if (targetPt != null) { + var span = targetPt.Value.Snapshot.CreateTrackingSpan(targetPt.Value.Position, 0, SpanTrackingMode.EdgeInclusive); + + var sigs = VsProjectAnalyzer.GetSignatures(targetPt.Value.Snapshot, span); + bool retrigger = false; + if (sigs.Signatures.Count == _sigHelpSession.Signatures.Count) { + for (int i = 0; i < sigs.Signatures.Count && !retrigger; i++) { + var leftSig = sigs.Signatures[i]; + var rightSig = _sigHelpSession.Signatures[i]; + + if (leftSig.Parameters.Count == rightSig.Parameters.Count) { + for (int j = 0; j < leftSig.Parameters.Count; j++) { + var leftParam = leftSig.Parameters[j]; + var rightParam = rightSig.Parameters[j]; + + if (leftParam.Name != rightParam.Name || leftParam.Documentation != rightParam.Documentation) { + retrigger = true; + break; + } + } + } + + if (leftSig.Content != rightSig.Content || leftSig.Documentation != rightSig.Documentation) { + retrigger = true; + } + } + } else { + retrigger = true; + } + + if (retrigger) { + _sigHelpSession.Dismiss(); + TriggerSignatureHelp(); + } else { + int curParam = sigs.ParameterIndex; + if (sigs.LastKeywordArgument != null) { + curParam = Int32.MaxValue; + for (int i = 0; i < sig.Parameters.Count; i++) { + if (sig.Parameters[i].Name == sigs.LastKeywordArgument) { + curParam = i; + break; + } + } + } + + if (curParam < sig.Parameters.Count) { + sig.SetCurrentParameter(sig.Parameters[curParam]); + } else if (sigs.LastKeywordArgument == String.Empty) { + sig.SetCurrentParameter(null); + } else { + CommaFindBestSignature(curParam, sigs.LastKeywordArgument); + } + } + } + } + } + + private void CommaFindBestSignature(int curParam, string lastKeywordArg) { + // see if we have a signature which accomodates this... + + // TODO: We should also take into account param arrays + // TODO: We should also get the types of the arguments and use that to + // pick the best signature when the signature includes types. + foreach (var availableSig in _sigHelpSession.Signatures) { + if (lastKeywordArg != null) { + for (int i = 0; i < availableSig.Parameters.Count; i++) { + if (availableSig.Parameters[i].Name == lastKeywordArg) { + _sigHelpSession.SelectedSignature = availableSig; + + NodejsSignature sig = availableSig as NodejsSignature; + if (sig != null) { + sig.SetCurrentParameter(sig.Parameters[i]); + } + break; + } + } + } else if (availableSig.Parameters.Count > curParam) { + _sigHelpSession.SelectedSignature = availableSig; + + NodejsSignature sig = availableSig as NodejsSignature; + if (sig != null) { + sig.SetCurrentParameter(sig.Parameters[curParam]); + } + break; + } + } + } + + internal void TriggerCompletionSession(bool completeWord) { + Dismiss(); + + _activeSession = CompletionBroker.TriggerCompletion(_textView); + + if (_activeSession != null) { + FuzzyCompletionSet set; + if (completeWord && + _activeSession.CompletionSets.Count == 1 && + (set = _activeSession.CompletionSets[0] as FuzzyCompletionSet) != null && + set.SelectSingleBest()) { + _activeSession.Commit(); + _activeSession = null; + } else { + _activeSession.Filter(); + _activeSession.Dismissed += OnCompletionSessionDismissedOrCommitted; + _activeSession.Committed += OnCompletionSessionDismissedOrCommitted; + } + } + } + + internal void TriggerSignatureHelp() { + if (_sigHelpSession != null) { + _sigHelpSession.Dismiss(); + } + + _sigHelpSession = SignatureBroker.TriggerSignatureHelp(_textView); + + if (_sigHelpSession != null) { + _sigHelpSession.Dismissed += OnSignatureSessionDismissed; + ISignature sig; + if (_sigHelpSession.Properties.TryGetProperty(typeof(NodejsSignature), out sig)) { + _sigHelpSession.SelectedSignature = sig; + + IParameter param; + if (_sigHelpSession.Properties.TryGetProperty(typeof(NodejsParameter), out param)) { + ((NodejsSignature)sig).SetCurrentParameter(param); + } + } + } + } + + private void OnCompletionSessionDismissedOrCommitted(object sender, System.EventArgs e) { + // We've just been told that our active session was dismissed. We should remove all references to it. + _activeSession.Committed -= OnCompletionSessionDismissedOrCommitted; + _activeSession.Dismissed -= OnCompletionSessionDismissedOrCommitted; + _activeSession = null; + } + + private void OnSignatureSessionDismissed(object sender, System.EventArgs e) { + // We've just been told that our active session was dismissed. We should remove all references to it. + _sigHelpSession.Dismissed -= OnSignatureSessionDismissed; + _sigHelpSession = null; + } + + private void DeleteSelectedSpans() { + if (!_textView.Selection.IsEmpty) { + _editOps.Delete(); + } + } + + private void Dismiss() { + if (_activeSession != null) { + _activeSession.Dismiss(); + } + } + + internal ICompletionBroker CompletionBroker { + get { + return _provider._CompletionBroker; + } + } + + internal IVsEditorAdaptersFactoryService AdaptersFactory { + get { + return _provider._adaptersFactory; + } + } + + internal ISignatureHelpBroker SignatureBroker { + get { + return _provider._SigBroker; + } + } + + #region IOleCommandTarget Members + + // we need this because VS won't give us certain keyboard events as they're handled before our key processor. These + // include enter and tab both of which we want to complete. + + internal void AttachKeyboardFilter() { + if (_oldTarget == null) { + var viewAdapter = AdaptersFactory.GetViewAdapter(_textView); + if (viewAdapter != null) { + ErrorHandler.ThrowOnFailure(viewAdapter.AddCommandFilter(this, out _oldTarget)); + } + } + } + + private void DetachKeyboardFilter() { + if (_oldTarget != null) { + ErrorHandler.ThrowOnFailure(AdaptersFactory.GetViewAdapter(_textView).RemoveCommandFilter(this)); + _oldTarget = null; + } + } + + private IVsTextView GetViewAdapter() { + return _provider._adaptersFactory.GetViewAdapter(_textView); + } + + public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (int)VSConstants.VSStd2KCmdID.TYPECHAR) { + var ch = (char)(ushort)System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant(pvaIn); + + if (_activeSession != null && !_activeSession.IsDismissed) { + if (_activeSession.SelectedCompletionSet.SelectionStatus.IsSelected) { + var completion = _activeSession.SelectedCompletionSet.SelectionStatus.Completion; + + string committedBy = String.Empty; + if (_activeSession.SelectedCompletionSet.Moniker == CompletionSource.NodejsRequireCompletionSetMoniker) { + if (completion.InsertionText.StartsWith("'")) { // require( + committedBy = ")"; + } else if (completion.InsertionText.EndsWith("'")) { // require(' + committedBy = "'"; + } else if (completion.InsertionText.EndsWith("\"")) { // require(" + committedBy = "\""; + } + } else { + committedBy = NodejsPackage.Instance != null && NodejsPackage.Instance.IntellisenseOptionsPage.OnlyTabOrEnterToCommit ? + string.Empty : + NodejsConstants.DefaultIntellisenseCompletionCommittedBy; + } + + if (committedBy.IndexOf(ch) != -1) { + _activeSession.Commit(); + if ((completion.InsertionText.EndsWith("'") && ch == '\'') || + (completion.InsertionText.EndsWith("\"") && ch == '"')) { + // https://nodejstools.codeplex.com/workitem/960 + // ' triggers the completion, but we don't want to insert the quote. + return VSConstants.S_OK; + } + } + } else if (_activeSession.SelectedCompletionSet.Moniker.Equals(CompletionSource.NodejsRequireCompletionSetMoniker) && !IsRequireIdentifierChar(ch)) { + _activeSession.Dismiss(); + } else if (!_activeSession.SelectedCompletionSet.Moniker.Equals(CompletionSource.NodejsRequireCompletionSetMoniker) && !IsIdentifierChar(ch)) { + _activeSession.Dismiss(); + } + } + + int res = _oldTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + + if (_activeSession == null || !_activeSession.SelectedCompletionSet.Moniker.Equals(CompletionSource.NodejsRequireCompletionSetMoniker)) { + //Only process the char if we are not in a require completion + HandleChar((char)(ushort)System.Runtime.InteropServices.Marshal.GetObjectForNativeVariant(pvaIn)); + } + + if (_activeSession != null && !_activeSession.IsDismissed) { + _activeSession.Filter(); + } + + return res; + } + + if (_activeSession != null) { + if (pguidCmdGroup == VSConstants.VSStd2K) { + switch ((VSConstants.VSStd2KCmdID)nCmdID) { + case VSConstants.VSStd2KCmdID.RETURN: + if (/*NodejsPackage.Instance.AdvancedEditorOptionsPage.EnterCommitsIntellisense*/ true && + !_activeSession.IsDismissed && + _activeSession.SelectedCompletionSet.SelectionStatus.IsSelected) { + + // If the user has typed all of the characters as the completion and presses + // enter we should dismiss & let the text editor receive the enter. For example + // when typing "import sys[ENTER]" completion starts after the space. After typing + // sys the user wants a new line and doesn't want to type enter twice. + + bool enterOnComplete = /*NodejsPackage.Instance.AdvancedEditorOptionsPage.AddNewLineAtEndOfFullyTypedWord*/true && + EnterOnCompleteText(); + + _activeSession.Commit(); + + if (!enterOnComplete) { + return VSConstants.S_OK; + } + } else { + _activeSession.Dismiss(); + } + break; + case VSConstants.VSStd2KCmdID.TAB: + if (!_activeSession.IsDismissed) { + _activeSession.Commit(); + return VSConstants.S_OK; + } + break; + case VSConstants.VSStd2KCmdID.BACKSPACE: + case VSConstants.VSStd2KCmdID.DELETE: + case VSConstants.VSStd2KCmdID.DELETEWORDLEFT: + case VSConstants.VSStd2KCmdID.DELETEWORDRIGHT: + int res = _oldTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + if (_activeSession != null && !_activeSession.IsDismissed) { + _activeSession.Filter(); + } + return res; + } + } + } else if (_sigHelpSession != null) { + if (pguidCmdGroup == VSConstants.VSStd2K) { + switch ((VSConstants.VSStd2KCmdID)nCmdID) { + case VSConstants.VSStd2KCmdID.BACKSPACE: + bool fDeleted = Backspace(); + if (fDeleted) { + return VSConstants.S_OK; + } + break; + case VSConstants.VSStd2KCmdID.LEFT: + _editOps.MoveToPreviousCharacter(false); + UpdateCurrentParameter(); + return VSConstants.S_OK; + case VSConstants.VSStd2KCmdID.RIGHT: + _editOps.MoveToNextCharacter(false); + UpdateCurrentParameter(); + return VSConstants.S_OK; + case VSConstants.VSStd2KCmdID.HOME: + case VSConstants.VSStd2KCmdID.BOL: + case VSConstants.VSStd2KCmdID.BOL_EXT: + case VSConstants.VSStd2KCmdID.EOL: + case VSConstants.VSStd2KCmdID.EOL_EXT: + case VSConstants.VSStd2KCmdID.END: + case VSConstants.VSStd2KCmdID.WORDPREV: + case VSConstants.VSStd2KCmdID.WORDPREV_EXT: + case VSConstants.VSStd2KCmdID.DELETEWORDLEFT: + _sigHelpSession.Dismiss(); + _sigHelpSession = null; + break; + } + } + } + if (pguidCmdGroup == VSConstants.VSStd2K) { + switch ((VSConstants.VSStd2KCmdID)nCmdID) { + case VSConstants.VSStd2KCmdID.QUICKINFO: + TriggerQuickInfo(); + return VSConstants.S_OK; + case VSConstants.VSStd2KCmdID.PARAMINFO: + TriggerSignatureHelp(); + return VSConstants.S_OK; + case VSConstants.VSStd2KCmdID.RETURN: + if (_expansionMgr != null && _expansionClient.InSession && ErrorHandler.Succeeded(_expansionClient.EndCurrentExpansion(false))) { + return VSConstants.S_OK; + } + break; + case VSConstants.VSStd2KCmdID.TAB: + if (_expansionMgr != null && _expansionClient.InSession && ErrorHandler.Succeeded(_expansionClient.NextField())) { + return VSConstants.S_OK; + } + if (_textView.Selection.IsEmpty && _textView.Caret.Position.BufferPosition > 0) { + if (TryTriggerExpansion()) { + return VSConstants.S_OK; + } + } + break; + case VSConstants.VSStd2KCmdID.BACKTAB: + if (_expansionMgr != null && _expansionClient.InSession && ErrorHandler.Succeeded(_expansionClient.PreviousField())) { + return VSConstants.S_OK; + } + break; + case VSConstants.VSStd2KCmdID.SURROUNDWITH: + case VSConstants.VSStd2KCmdID.INSERTSNIPPET: + TriggerSnippet(nCmdID); + return VSConstants.S_OK; + case VSConstants.VSStd2KCmdID.SHOWMEMBERLIST: + case VSConstants.VSStd2KCmdID.COMPLETEWORD: + ForceCompletions = true; + try { + TriggerCompletionSession((VSConstants.VSStd2KCmdID)nCmdID == VSConstants.VSStd2KCmdID.COMPLETEWORD + && !NodejsPackage.Instance.IntellisenseOptionsPage.OnlyTabOrEnterToCommit); + } finally { + ForceCompletions = false; + } + return VSConstants.S_OK; + } + } + return _oldTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + } + + private void TriggerSnippet(uint nCmdID) { + if (_expansionMgr != null) { + string prompt; + string[] snippetTypes; + if ((VSConstants.VSStd2KCmdID)nCmdID == VSConstants.VSStd2KCmdID.SURROUNDWITH) { + prompt = SR.GetString(SR.SurroundWith); + snippetTypes = _surroundsWithSnippetTypes; + } else { + prompt = SR.GetString(SR.InsertSnippet); + snippetTypes = _allStandardSnippetTypes; + } + + _expansionMgr.InvokeInsertionUI( + GetViewAdapter(), + _expansionClient, + Guids.NodejsLanguageInfo, + snippetTypes, + snippetTypes.Length, + 0, + null, + 0, + 0, + prompt, + ">" + ); + } + } + + private bool TryTriggerExpansion() { + if (_expansionMgr != null) { + var snapshot = _textView.TextBuffer.CurrentSnapshot; + var span = new SnapshotSpan(snapshot, new Span(_textView.Caret.Position.BufferPosition.Position - 1, 1)); + var classification = _textView.TextBuffer.GetNodejsClassifier().GetClassificationSpans(span); + if (classification.Count == 1) { + var clsSpan = classification.First().Span; + var text = classification.First().Span.GetText(); + + TextSpan[] textSpan = new TextSpan[1]; + textSpan[0].iStartLine = clsSpan.Start.GetContainingLine().LineNumber; + textSpan[0].iStartIndex = clsSpan.Start.Position - clsSpan.Start.GetContainingLine().Start; + textSpan[0].iEndLine = clsSpan.End.GetContainingLine().LineNumber; + textSpan[0].iEndIndex = clsSpan.End.Position - clsSpan.End.GetContainingLine().Start; + + string expansionPath, title; + int hr = _expansionMgr.GetExpansionByShortcut( + _expansionClient, + Guids.NodejsLanguageInfo, + text, + GetViewAdapter(), + textSpan, + 1, + out expansionPath, + out title + ); + if (ErrorHandler.Succeeded(hr)) { + // hr may be S_FALSE if there are multiple expansions, + // so we don't want to InsertNamedExpansion yet. VS will + // pop up a selection dialog in this case. + if (hr == VSConstants.S_OK) { + return ErrorHandler.Succeeded(_expansionClient.InsertNamedExpansion(title, expansionPath, textSpan[0])); + } + return true; + } + } + } + return false; + } + + private static bool IsRequireIdentifierChar(char ch) { + return ch == '_' + || ch == '.' + || ch == '/' + || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9'); + } + + private static bool IsIdentifierFirstChar(char ch) { + return ch == '_' || ch == '$'|| (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); + } + + private static bool IsIdentifierChar(char ch) { + return IsIdentifierFirstChar(ch) || (ch >= '0' && ch <= '9'); + } + + private bool EnterOnCompleteText() { + SnapshotPoint? point = _activeSession.GetTriggerPoint(_textView.TextBuffer.CurrentSnapshot); + if (point.HasValue) { + int chars = _textView.Caret.Position.BufferPosition.Position - point.Value.Position; + var selectionStatus = _activeSession.SelectedCompletionSet.SelectionStatus; + if (chars == selectionStatus.Completion.InsertionText.Length) { + string text = _textView.TextSnapshot.GetText(point.Value.Position, chars); + + if (String.Compare(text, selectionStatus.Completion.InsertionText, true) == 0) { + return true; + } + } + } + + return false; + } + + public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { + if (pguidCmdGroup == VSConstants.VSStd2K) { + for (int i = 0; i < cCmds; i++) { + switch ((VSConstants.VSStd2KCmdID)prgCmds[i].cmdID) { + case VSConstants.VSStd2KCmdID.SHOWMEMBERLIST: + case VSConstants.VSStd2KCmdID.COMPLETEWORD: + case VSConstants.VSStd2KCmdID.QUICKINFO: + case VSConstants.VSStd2KCmdID.PARAMINFO: + case VSConstants.VSStd2KCmdID.SURROUNDWITH: + case VSConstants.VSStd2KCmdID.INSERTSNIPPET: + prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED); + return VSConstants.S_OK; + } + } + } + + return _oldTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + } + + #endregion + } +} + diff --git a/Nodejs/Product/Nodejs/Intellisense/IntellisenseControllerProvider.cs b/Nodejs/Product/Nodejs/Intellisense/IntellisenseControllerProvider.cs index 8316f41da..279b1108c 100644 --- a/Nodejs/Product/Nodejs/Intellisense/IntellisenseControllerProvider.cs +++ b/Nodejs/Product/Nodejs/Intellisense/IntellisenseControllerProvider.cs @@ -27,8 +27,8 @@ using Microsoft.VisualStudio.Text.IncrementalSearch; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; -using Microsoft.VisualStudio.Shell; - +using Microsoft.VisualStudio.Shell; + namespace Microsoft.NodejsTools.Intellisense { [Export(typeof(IIntellisenseControllerProvider)), ContentType(NodejsConstants.Nodejs), Order] class IntellisenseControllerProvider : IIntellisenseControllerProvider { diff --git a/Nodejs/Product/Nodejs/Intellisense/TaskProvider.cs b/Nodejs/Product/Nodejs/Intellisense/TaskProvider.cs index 8fd07d34d..1a0df2484 100644 --- a/Nodejs/Product/Nodejs/Intellisense/TaskProvider.cs +++ b/Nodejs/Product/Nodejs/Intellisense/TaskProvider.cs @@ -296,15 +296,15 @@ public TaskProvider(IServiceProvider serviceProvider, IVsTaskList errorList, IEr public void Dispose() { lock (_workerQueue) { - try { - if (_hasWorker) { - _hasWorker = false; - _workerQueue.CompleteAdding(); - } else { - _workerQueue.Dispose(); - } - } catch (ObjectDisposedException) { - // _workerQueue is already disposed + try { + if (_hasWorker) { + _hasWorker = false; + _workerQueue.CompleteAdding(); + } else { + _workerQueue.Dispose(); + } + } catch (ObjectDisposedException) { + // _workerQueue is already disposed } } lock (_itemsLock) { @@ -466,14 +466,14 @@ private void Worker() { msg.Apply(_items, _itemsLock); } - lock(_workerQueue) { - try { - if (_workerQueue.IsCompleted) { - _workerQueue.Dispose(); - } - } catch (ObjectDisposedException) { - // We are already disposed - } + lock(_workerQueue) { + try { + if (_workerQueue.IsCompleted) { + _workerQueue.Dispose(); + } + } catch (ObjectDisposedException) { + // We are already disposed + } } } @@ -553,8 +553,8 @@ await _serviceProvider.GetUIThread().InvokeAsync(() => { private void SendMessage(WorkerMessage message) { lock (_workerQueue) { try { - if (_workerQueue.IsAddingCompleted) { - return; + if (_workerQueue.IsAddingCompleted) { + return; } _workerQueue.Add(message); } catch (ObjectDisposedException) { diff --git a/Nodejs/Product/Nodejs/Logging/LiveLogger.cs b/Nodejs/Product/Nodejs/Logging/LiveLogger.cs index dd9a49115..67658f9fe 100644 --- a/Nodejs/Product/Nodejs/Logging/LiveLogger.cs +++ b/Nodejs/Product/Nodejs/Logging/LiveLogger.cs @@ -1,87 +1,87 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Microsoft.NodejsTools.Options; -using Microsoft.VisualStudioTools.Project; -using System; -using System.Diagnostics; - -namespace Microsoft.NodejsTools.Logging { - - /// - /// An efficient logger that logs diagnostic messages using Debug.WriteLine. - /// Additionally logs messages to the NTVS Diagnostics task pane if option is enabled. - /// - internal sealed class LiveLogger { - private static Guid LiveDiagnosticLogPaneGuid = new Guid("{66386208-2E7E-4B93-A852-D1A32EE00107}"); - private const string LiveDiagnosticLogPaneName = "Node.js Tools Live Diagnostics"; - - private static volatile LiveLogger _instance; - private static object _loggerLock = new object(); - - private NodejsDiagnosticsOptionsPage _diagnosticsOptions; - - private LiveLogger() { - } - - private NodejsDiagnosticsOptionsPage DiagnosticsOptions { - get { - if (_diagnosticsOptions == null && NodejsPackage.Instance != null) { - _diagnosticsOptions = NodejsPackage.Instance.DiagnosticsOptionsPage; - } - return _diagnosticsOptions; - } - } - - private static LiveLogger Instance { - get { - if (_instance == null) { - lock (_loggerLock) { - if (_instance == null) { - _instance = new LiveLogger(); - } - } - } - return _instance; - } - } - - public static void WriteLine(string message, Type category) { - WriteLine("{0}: {1}", category.Name, message); - } - - public static void WriteLine(string message) { - var str = String.Format("[{0}] {1}", DateTime.UtcNow.TimeOfDay, message); - Instance.LogMessage(str); - } - - public static void WriteLine(string format, params object[] args) { - var str = String.Format(format, args); - WriteLine(str); - } - - private void LogMessage(string message) { - Debug.WriteLine(message); - - if (DiagnosticsOptions != null && DiagnosticsOptions.IsLiveDiagnosticsEnabled) { - var pane = OutputWindowRedirector.Get(VisualStudio.Shell.ServiceProvider.GlobalProvider, LiveDiagnosticLogPaneGuid, LiveDiagnosticLogPaneName); - if (pane != null) { - pane.WriteLine(message); - } - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Microsoft.NodejsTools.Options; +using Microsoft.VisualStudioTools.Project; +using System; +using System.Diagnostics; + +namespace Microsoft.NodejsTools.Logging { + + /// + /// An efficient logger that logs diagnostic messages using Debug.WriteLine. + /// Additionally logs messages to the NTVS Diagnostics task pane if option is enabled. + /// + internal sealed class LiveLogger { + private static Guid LiveDiagnosticLogPaneGuid = new Guid("{66386208-2E7E-4B93-A852-D1A32EE00107}"); + private const string LiveDiagnosticLogPaneName = "Node.js Tools Live Diagnostics"; + + private static volatile LiveLogger _instance; + private static object _loggerLock = new object(); + + private NodejsDiagnosticsOptionsPage _diagnosticsOptions; + + private LiveLogger() { + } + + private NodejsDiagnosticsOptionsPage DiagnosticsOptions { + get { + if (_diagnosticsOptions == null && NodejsPackage.Instance != null) { + _diagnosticsOptions = NodejsPackage.Instance.DiagnosticsOptionsPage; + } + return _diagnosticsOptions; + } + } + + private static LiveLogger Instance { + get { + if (_instance == null) { + lock (_loggerLock) { + if (_instance == null) { + _instance = new LiveLogger(); + } + } + } + return _instance; + } + } + + public static void WriteLine(string message, Type category) { + WriteLine("{0}: {1}", category.Name, message); + } + + public static void WriteLine(string message) { + var str = String.Format("[{0}] {1}", DateTime.UtcNow.TimeOfDay, message); + Instance.LogMessage(str); + } + + public static void WriteLine(string format, params object[] args) { + var str = String.Format(format, args); + WriteLine(str); + } + + private void LogMessage(string message) { + Debug.WriteLine(message); + + if (DiagnosticsOptions != null && DiagnosticsOptions.IsLiveDiagnosticsEnabled) { + var pane = OutputWindowRedirector.Get(VisualStudio.Shell.ServiceProvider.GlobalProvider, LiveDiagnosticLogPaneGuid, LiveDiagnosticLogPaneName); + if (pane != null) { + pane.WriteLine(message); + } + } + } + } +} diff --git a/Nodejs/Product/Nodejs/Nodejs.cs b/Nodejs/Product/Nodejs/Nodejs.cs index 32d05f2a6..140719cca 100644 --- a/Nodejs/Product/Nodejs/Nodejs.cs +++ b/Nodejs/Product/Nodejs/Nodejs.cs @@ -1,140 +1,140 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.IO; -#if !NO_WINDOWS -using System.Windows.Forms; -#endif -using Microsoft.Win32; -#if !NO_WINDOWS -using Microsoft.NodejsTools.Project; -#endif -using Microsoft.VisualStudioTools; - -namespace Microsoft.NodejsTools { - public sealed class Nodejs { - private const string NodejsRegPath = "Software\\Node.js"; - private const string InstallPath = "InstallPath"; - - public static string NodeExePath { - get { - return GetPathToNodeExecutableFromEnvironment(); - } - } - - public static string GetAbsoluteNodeExePath(string root, string relativePath) { - var overridePath = CommonUtils.UnquotePath(relativePath ?? string.Empty); - if (!String.IsNullOrWhiteSpace(overridePath)) { - if (string.IsNullOrWhiteSpace(root)) { - return relativePath; - } - try { - return CommonUtils.GetAbsoluteFilePath(root, overridePath); - } catch (InvalidOperationException) { - return relativePath; - } - } - return NodeExePath; - } - - public static string GetPathToNodeExecutableFromEnvironment(string executable = "node.exe") { - // Attempt to find Node.js/NPM in the Registry. (Currrent User) - using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)) - using (var node = baseKey.OpenSubKey(NodejsRegPath)) { - if (node != null) { - string key = (node.GetValue(InstallPath) as string) ?? string.Empty; - var execPath = Path.Combine(key, executable); - if (File.Exists(execPath)) { - return execPath; - } - } - } - - // Attempt to find Node.js/NPM in the Registry. (Local Machine x64) - if (Environment.Is64BitOperatingSystem) { - using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) - using (var node64 = baseKey.OpenSubKey(NodejsRegPath)) { - if (node64 != null) { - string key = (node64.GetValue(InstallPath) as string) ?? string.Empty; - var execPath = Path.Combine(key, executable); - if (File.Exists(execPath)) { - return execPath; - } - } - } - } - - // Attempt to find Node.js/NPM in the Registry. (Local Machine x86) - using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) - using (var node = baseKey.OpenSubKey(NodejsRegPath)) { - if (node != null) { - string key = (node.GetValue(InstallPath) as string) ?? string.Empty; - var execPath = Path.Combine(key, executable); - if (File.Exists(execPath)) { - return execPath; - } - } - } - - // If we didn't find node.js in the registry we should look at the user's path. - foreach (var dir in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator)) { - var execPath = Path.Combine(dir, executable); - if (File.Exists(execPath)) { - return execPath; - } - } - - // It wasn't in the users path. Check Program Files for the nodejs folder. - string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "nodejs", executable); - if (File.Exists(path)) { - return path; - } - - // It wasn't in the users path. Check Program Files x86 for the nodejs folder. - var x86path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); - if (!String.IsNullOrEmpty(x86path)) { - path = Path.Combine(x86path, "nodejs", executable); - if (File.Exists(path)) { - return path; - } - } - - // we didn't find the path. - return null; - } - -#if !NO_WINDOWS - public static void ShowNodejsNotInstalled() { - MessageBox.Show( - SR.GetString(SR.NodejsNotInstalled), - SR.ProductName, - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - } - - public static void ShowNodejsPathNotFound(string path) { - MessageBox.Show( - SR.GetString(SR.NodeExeDoesntExist, path), - SR.ProductName, - MessageBoxButtons.OK, - MessageBoxIcon.Error - ); - } -#endif - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.IO; +#if !NO_WINDOWS +using System.Windows.Forms; +#endif +using Microsoft.Win32; +#if !NO_WINDOWS +using Microsoft.NodejsTools.Project; +#endif +using Microsoft.VisualStudioTools; + +namespace Microsoft.NodejsTools { + public sealed class Nodejs { + private const string NodejsRegPath = "Software\\Node.js"; + private const string InstallPath = "InstallPath"; + + public static string NodeExePath { + get { + return GetPathToNodeExecutableFromEnvironment(); + } + } + + public static string GetAbsoluteNodeExePath(string root, string relativePath) { + var overridePath = CommonUtils.UnquotePath(relativePath ?? string.Empty); + if (!String.IsNullOrWhiteSpace(overridePath)) { + if (string.IsNullOrWhiteSpace(root)) { + return relativePath; + } + try { + return CommonUtils.GetAbsoluteFilePath(root, overridePath); + } catch (InvalidOperationException) { + return relativePath; + } + } + return NodeExePath; + } + + public static string GetPathToNodeExecutableFromEnvironment(string executable = "node.exe") { + // Attempt to find Node.js/NPM in the Registry. (Currrent User) + using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Default)) + using (var node = baseKey.OpenSubKey(NodejsRegPath)) { + if (node != null) { + string key = (node.GetValue(InstallPath) as string) ?? string.Empty; + var execPath = Path.Combine(key, executable); + if (File.Exists(execPath)) { + return execPath; + } + } + } + + // Attempt to find Node.js/NPM in the Registry. (Local Machine x64) + if (Environment.Is64BitOperatingSystem) { + using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) + using (var node64 = baseKey.OpenSubKey(NodejsRegPath)) { + if (node64 != null) { + string key = (node64.GetValue(InstallPath) as string) ?? string.Empty; + var execPath = Path.Combine(key, executable); + if (File.Exists(execPath)) { + return execPath; + } + } + } + } + + // Attempt to find Node.js/NPM in the Registry. (Local Machine x86) + using (var baseKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) + using (var node = baseKey.OpenSubKey(NodejsRegPath)) { + if (node != null) { + string key = (node.GetValue(InstallPath) as string) ?? string.Empty; + var execPath = Path.Combine(key, executable); + if (File.Exists(execPath)) { + return execPath; + } + } + } + + // If we didn't find node.js in the registry we should look at the user's path. + foreach (var dir in Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator)) { + var execPath = Path.Combine(dir, executable); + if (File.Exists(execPath)) { + return execPath; + } + } + + // It wasn't in the users path. Check Program Files for the nodejs folder. + string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), "nodejs", executable); + if (File.Exists(path)) { + return path; + } + + // It wasn't in the users path. Check Program Files x86 for the nodejs folder. + var x86path = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86); + if (!String.IsNullOrEmpty(x86path)) { + path = Path.Combine(x86path, "nodejs", executable); + if (File.Exists(path)) { + return path; + } + } + + // we didn't find the path. + return null; + } + +#if !NO_WINDOWS + public static void ShowNodejsNotInstalled() { + MessageBox.Show( + SR.GetString(SR.NodejsNotInstalled), + SR.ProductName, + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } + + public static void ShowNodejsPathNotFound(string path) { + MessageBox.Show( + SR.GetString(SR.NodeExeDoesntExist, path), + SR.ProductName, + MessageBoxButtons.OK, + MessageBoxIcon.Error + ); + } +#endif + } +} diff --git a/Nodejs/Product/Nodejs/NodejsPackage.cs b/Nodejs/Product/Nodejs/NodejsPackage.cs index 2dac7dbb8..1678ec38f 100644 --- a/Nodejs/Product/Nodejs/NodejsPackage.cs +++ b/Nodejs/Product/Nodejs/NodejsPackage.cs @@ -1,822 +1,822 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.ComponentModel.Design; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Threading; -using System.Web.Script.Serialization; -using System.Windows.Forms; -using Microsoft.NodejsTools.Analysis; -using Microsoft.NodejsTools.Commands; -using Microsoft.NodejsTools.Debugger.DataTips; -using Microsoft.NodejsTools.Debugger.DebugEngine; -using Microsoft.NodejsTools.Debugger.Remote; -using Microsoft.NodejsTools.Intellisense; -using Microsoft.NodejsTools.Jade; -using Microsoft.NodejsTools.Logging; -using Microsoft.NodejsTools.Options; -using Microsoft.NodejsTools.Project; -using Microsoft.NodejsTools.ProjectWizard; -using Microsoft.NodejsTools.Repl; -using Microsoft.NodejsTools.Telemetry; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.ComponentModelHost; -using Microsoft.VisualStudio.Debugger.Interop; -using Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.Utilities; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.Project; -using Microsoft.Win32; - -namespace Microsoft.NodejsTools { - /// - /// This is the class that implements the package exposed by this assembly. - /// - /// The minimum requirement for a class to be considered a valid package for Visual Studio - /// is to implement the IVsPackage interface and register itself with the shell. - /// This package uses the helper classes defined inside the Managed Package Framework (MPF) - /// to do it: it derives from the Package class that provides the implementation of the - /// IVsPackage interface and uses the registration attributes defined in the framework to - /// register itself and its components with the shell. - /// - // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is - // a package. - [PackageRegistration(UseManagedResourcesOnly = true)] - // This attribute is used to register the information needed to show this package - // in the Help/About dialog of Visual Studio. - [InstalledProductRegistration("#110", "#112", AssemblyVersionInfo.Version, IconResourceID = 400)] - [Guid(Guids.NodejsPackageString)] - [ProvideOptionPage(typeof(NodejsGeneralOptionsPage), "Node.js Tools", "General", 114, 115, true)] - [ProvideOptionPage(typeof(NodejsNpmOptionsPage), "Node.js Tools", "Npm", 114, 116, true)] - [ProvideDebugEngine("Node.js Debugging", typeof(AD7ProgramProvider), typeof(AD7Engine), AD7Engine.DebugEngineId, setNextStatement: false, hitCountBp: true, justMyCodeStepping: false)] - [ProvideLanguageService(typeof(NodejsLanguageInfo), NodejsConstants.Nodejs, 106, RequestStockColors = true, ShowSmartIndent = true, ShowCompletion = true, DefaultToInsertSpaces = true, HideAdvancedMembersByDefault = true, EnableAdvancedMembersOption = true, ShowDropDownOptions = true)] - [ProvideDebugLanguage(NodejsConstants.Nodejs, Guids.NodejsDebugLanguageString, NodeExpressionEvaluatorGuid, AD7Engine.DebugEngineId)] - [WebSiteProject("JavaScript", "JavaScript")] - [ProvideProjectFactory(typeof(NodejsProjectFactory), null, null, null, null, ".\\NullPath", LanguageVsTemplate = NodejsConstants.JavaScript, SortPriority=0x17)] // outer flavor, no file extension - [ProvideDebugPortSupplier("Node remote debugging", typeof(NodeRemoteDebugPortSupplier), NodeRemoteDebugPortSupplier.PortSupplierId)] - [ProvideMenuResource(1000, 1)] // This attribute is needed to let the shell know that this package exposes some menus. - [ProvideBraceCompletion(NodejsConstants.Nodejs)] - [ProvideEditorExtension2(typeof(NodejsEditorFactory), NodeJsFileType, 50, "*:1", ProjectGuid = "{78D985FC-2CA0-4D08-9B6B-35ACD5E5294A}", NameResourceID = 102, DefaultName = "server", TemplateDir = ".\\NullPath")] - [ProvideEditorExtension2(typeof(NodejsEditorFactoryPromptForEncoding), NodeJsFileType, 50, "*:1", ProjectGuid = "{78D985FC-2CA0-4D08-9B6B-35ACD5E5294A}", NameResourceID = 113, DefaultName = "server")] - [ProvideEditorLogicalView(typeof(NodejsEditorFactory), VSConstants.LOGVIEWID.TextView_string)] - [ProvideEditorLogicalView(typeof(NodejsEditorFactoryPromptForEncoding), VSConstants.LOGVIEWID.TextView_string)] - [ProvideProjectItem(typeof(BaseNodeProjectFactory), NodejsConstants.Nodejs, "FileTemplates\\NewItem", 0)] - [ProvideLanguageTemplates("{349C5851-65DF-11DA-9384-00065B846F21}", NodejsConstants.JavaScript, Guids.NodejsPackageString, "Web", "Node.js Project Templates", "{" + Guids.NodejsBaseProjectFactoryString + "}", ".js", NodejsConstants.Nodejs, "{" + Guids.NodejsBaseProjectFactoryString + "}")] - [ProvideTextEditorAutomation(NodejsConstants.Nodejs, 106, 102, ProfileMigrationType.PassThrough)] - [ProvideLanguageService(typeof(JadeLanguageInfo), JadeContentTypeDefinition.JadeLanguageName, 3041, RequestStockColors = true, ShowSmartIndent = false, ShowCompletion = false, DefaultToInsertSpaces = true, HideAdvancedMembersByDefault = false, EnableAdvancedMembersOption = false, ShowDropDownOptions = false)] - [ProvideEditorExtension2(typeof(JadeEditorFactory), JadeContentTypeDefinition.JadeFileExtension, 50, __VSPHYSICALVIEWATTRIBUTES.PVA_SupportsPreview, "*:1", ProjectGuid = VSConstants.CLSID.MiscellaneousFilesProject_string, NameResourceID = 3041, EditorNameResourceId = 3045)] - [ProvideEditorLogicalView(typeof(JadeEditorFactory), VSConstants.LOGVIEWID.TextView_string)] - [ProvideLanguageExtension(typeof(JadeEditorFactory), JadeContentTypeDefinition.JadeFileExtension)] - [ProvideTextEditorAutomation(JadeContentTypeDefinition.JadeLanguageName, 3041, 3045, ProfileMigrationType.PassThrough)] - [ProvideLanguageEditorOptionPage(typeof(NodejsFormattingSpacingOptionsPage), NodejsConstants.Nodejs, "Formatting", "Spacing", "3042")] - [ProvideLanguageEditorOptionPage(typeof(NodejsFormattingBracesOptionsPage), NodejsConstants.Nodejs, "Formatting", "Braces", "3043")] - [ProvideLanguageEditorOptionPage(typeof(NodejsFormattingGeneralOptionsPage), NodejsConstants.Nodejs, "Formatting", "General", "3044")] - [ProvideLanguageEditorOptionPage(typeof(NodejsIntellisenseOptionsPage), NodejsConstants.Nodejs, "IntelliSense", "", "3048")] - [ProvideLanguageEditorOptionPage(typeof(NodejsAdvancedEditorOptionsPage), NodejsConstants.Nodejs, "Advanced", "", "3050")] - [ProvideCodeExpansions(Guids.NodejsLanguageInfoString, false, 106, "Nodejs", @"Snippets\%LCID%\SnippetsIndex.xml", @"Snippets\%LCID%\Nodejs\")] - [ProvideCodeExpansionPath("Nodejs", "Test", @"Snippets\%LCID%\Test\")] - internal sealed partial class NodejsPackage : CommonPackage { - internal const string NodeExpressionEvaluatorGuid = "{F16F2A71-1C45-4BAB-BECE-09D28CFDE3E6}"; - private IContentType _contentType; - internal const string NodeJsFileType = ".njs"; - internal static NodejsPackage Instance; - private string _surveyNewsUrl; - private object _surveyNewsUrlLock = new object(); - internal HashSet ChangedBuffers = new HashSet(); - private LanguagePreferences _langPrefs; - internal VsProjectAnalyzer _analyzer; - private NodejsToolsLogger _logger; - private ITelemetryLogger _telemetryLogger; - // Hold references for the subscribed events. Otherwise the callbacks will be garbage collected - // after the initialization - private List _subscribedCommandEvents = new List(); - - /// - /// Default constructor of the package. - /// Inside this method you can place any initialization code that does not require - /// any Visual Studio service because at this point the package object is created but - /// not sited yet inside Visual Studio environment. The place to do all the other - /// initialization is the Initialize method. - /// - public NodejsPackage() { - Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString())); - Debug.Assert(Instance == null, "NodejsPackage created multiple times"); - Instance = this; - } - - public NodejsGeneralOptionsPage GeneralOptionsPage { - get { - return (NodejsGeneralOptionsPage)GetDialogPage(typeof(NodejsGeneralOptionsPage)); - } - } - - public NodejsNpmOptionsPage NpmOptionsPage { - get { - return (NodejsNpmOptionsPage)GetDialogPage(typeof(NodejsNpmOptionsPage)); - } - } - - public NodejsFormattingSpacingOptionsPage FormattingSpacingOptionsPage { - get { - return (NodejsFormattingSpacingOptionsPage)GetDialogPage(typeof(NodejsFormattingSpacingOptionsPage)); - } - } - - public NodejsFormattingBracesOptionsPage FormattingBracesOptionsPage { - get { - return (NodejsFormattingBracesOptionsPage)GetDialogPage(typeof(NodejsFormattingBracesOptionsPage)); - } - } - - public NodejsFormattingGeneralOptionsPage FormattingGeneralOptionsPage { - get { - return (NodejsFormattingGeneralOptionsPage)GetDialogPage(typeof(NodejsFormattingGeneralOptionsPage)); - } - } - - public NodejsIntellisenseOptionsPage IntellisenseOptionsPage { - get { - return (NodejsIntellisenseOptionsPage)GetDialogPage(typeof(NodejsIntellisenseOptionsPage)); - } - } - - public NodejsAdvancedEditorOptionsPage AdvancedEditorOptionsPage { - get { - return (NodejsAdvancedEditorOptionsPage)GetDialogPage(typeof(NodejsAdvancedEditorOptionsPage)); - } - } - - public NodejsDiagnosticsOptionsPage DiagnosticsOptionsPage { - get { - return (NodejsDiagnosticsOptionsPage)GetDialogPage(typeof(NodejsDiagnosticsOptionsPage)); - } - } - - public EnvDTE.DTE DTE { - get { - return (EnvDTE.DTE)GetService(typeof(EnvDTE.DTE)); - } - } - - ///////////////////////////////////////////////////////////////////////////// - // Overridden Package Implementation - #region Package Members - - /// - /// Initialization of the package; this method is called right after the package is sited, so this is the place - /// where you can put all the initialization code that rely on services provided by VisualStudio. - /// - protected override void Initialize() { - Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString())); - base.Initialize(); - - SubscribeToVsCommandEvents( - (int)VSConstants.VSStd97CmdID.AddNewProject, - delegate { NewProjectFromExistingWizard.IsAddNewProjectCmd = true; }, - delegate { NewProjectFromExistingWizard.IsAddNewProjectCmd = false; } - ); - - var langService = new NodejsLanguageInfo(this); - ((IServiceContainer)this).AddService(langService.GetType(), langService, true); - - ((IServiceContainer)this).AddService(typeof(ClipboardServiceBase), new ClipboardService(), true); - - RegisterProjectFactory(new NodejsProjectFactory(this)); - RegisterEditorFactory(new NodejsEditorFactory(this)); - RegisterEditorFactory(new NodejsEditorFactoryPromptForEncoding(this)); - RegisterEditorFactory(new JadeEditorFactory(this)); - - // Add our command handlers for menu (commands must exist in the .vsct file) - var commands = new List { - new OpenReplWindowCommand(), - new OpenRemoteDebugProxyFolderCommand(), - new OpenRemoteDebugDocumentationCommand(), - new SurveyNewsCommand(), - new ImportWizardCommand(), - new DiagnosticsCommand(this) - }; - try { - commands.Add(new AzureExplorerAttachDebuggerCommand()); - } catch (NotSupportedException) { - } - RegisterCommands(commands, Guids.NodejsCmdSet); - - IVsTextManager textMgr = (IVsTextManager)Instance.GetService(typeof(SVsTextManager)); - - var langPrefs = new LANGPREFERENCES[1]; - langPrefs[0].guidLang = typeof(NodejsLanguageInfo).GUID; - ErrorHandler.ThrowOnFailure(textMgr.GetUserPreferences(null, null, langPrefs, null)); - _langPrefs = new LanguagePreferences(langPrefs[0]); - - var textManagerEvents2Guid = typeof(IVsTextManagerEvents2).GUID; - IConnectionPoint textManagerEvents2ConnectionPoint; - ((IConnectionPointContainer)textMgr).FindConnectionPoint(ref textManagerEvents2Guid, out textManagerEvents2ConnectionPoint); - uint cookie; - textManagerEvents2ConnectionPoint.Advise(_langPrefs, out cookie); - - var textManagerEventsGuid = typeof(IVsTextManagerEvents).GUID; - IConnectionPoint textManagerEventsConnectionPoint; - ((IConnectionPointContainer)textMgr).FindConnectionPoint(ref textManagerEventsGuid, out textManagerEventsConnectionPoint); - textManagerEventsConnectionPoint.Advise(new DataTipTextManagerEvents(this), out cookie); - - MakeDebuggerContextAvailable(); - - IntellisenseOptionsPage.AnalysisLogMaximumChanged += IntellisenseOptionsPage_AnalysisLogMaximumChanged; - - InitializeLogging(); - - InitializeTelemetry(); - - // The variable is inherited by child processes backing Test Explorer, and is used in - // the NTVS test discoverer and test executor to connect back to VS. - Environment.SetEnvironmentVariable(NodejsConstants.NodeToolsProcessIdEnvironmentVariable, Process.GetCurrentProcess().Id.ToString()); - } - - private void SubscribeToVsCommandEvents( - int eventId, - EnvDTE._dispCommandEvents_BeforeExecuteEventHandler beforeExecute = null, - EnvDTE._dispCommandEvents_AfterExecuteEventHandler afterExecute = null) { - var commandEventGuid = typeof(VSConstants.VSStd97CmdID).GUID.ToString("B"); - var targetEvent = DTE.Events.CommandEvents[commandEventGuid, eventId]; - if (beforeExecute != null) { - targetEvent.BeforeExecute += beforeExecute; - } - if (afterExecute != null) { - targetEvent.AfterExecute += afterExecute; - } - _subscribedCommandEvents.Add(targetEvent); - } - - - private void IntellisenseOptionsPage_AnalysisLogMaximumChanged(object sender, EventArgs e) { - if (_analyzer != null) { - _analyzer.MaxLogLength = IntellisenseOptionsPage.AnalysisLogMax; - } - } - - private void InitializeLogging() { - _logger = new NodejsToolsLogger(ComponentModel.GetExtensions().ToArray()); - - // log interesting stats on startup - _logger.LogEvent(NodejsToolsLogEvent.SurveyNewsFrequency, GeneralOptionsPage.SurveyNewsCheck); - _logger.LogEvent(NodejsToolsLogEvent.AnalysisLevel, IntellisenseOptionsPage.AnalysisLevel); - } - - private void InitializeTelemetry() { - var thisAssembly = typeof(NodejsPackage).Assembly; - - // Get telemetry logger - _telemetryLogger = TelemetrySetup.Instance.GetLogger(thisAssembly); - - TelemetrySetup.Instance.LogPackageLoad(_telemetryLogger, Guid.Parse(Guids.NodejsPackageString), thisAssembly, Application.ProductVersion); - } - - public new IComponentModel ComponentModel { - get { - return this.GetComponentModel(); - } - } - - internal NodejsToolsLogger Logger { - get { - return _logger; - } - } - - internal ITelemetryLogger TelemetryLogger { - get { - return _telemetryLogger; - } - } - - /// - /// Makes the debugger context available - this enables our debugger when we're installed into - /// a SKU which doesn't support every installed debugger. - /// - private void MakeDebuggerContextAvailable() { - var monitorSelection = (IVsMonitorSelection)GetService(typeof(SVsShellMonitorSelection)); - Guid debugEngineGuid = AD7Engine.DebugEngineGuid; - uint contextCookie; - if (ErrorHandler.Succeeded(monitorSelection.GetCmdUIContextCookie(ref debugEngineGuid, out contextCookie))) { - ErrorHandler.ThrowOnFailure(monitorSelection.SetCmdUIContext(contextCookie, 1)); - } - } - - internal IReplWindow2 OpenReplWindow(bool focus = true) { - var compModel = ComponentModel; - var provider = compModel.GetService(); - - var window = (IReplWindow2)provider.FindReplWindow(NodejsReplEvaluatorProvider.NodeReplId); - if (window == null) { - window = (IReplWindow2)provider.CreateReplWindow( - ReplContentType, - "Node.js Interactive Window", - typeof(NodejsLanguageInfo).GUID, - NodejsReplEvaluatorProvider.NodeReplId - ); - } - - IVsWindowFrame windowFrame = (IVsWindowFrame)((ToolWindowPane)window).Frame; - ErrorHandler.ThrowOnFailure(windowFrame.Show()); - - if (focus) { - window.Focus(); - } - - return window; - } - - internal static bool TryGetStartupFileAndDirectory(System.IServiceProvider serviceProvider, out string fileName, out string directory) { - var startupProject = GetStartupProject(serviceProvider); - if (startupProject != null) { - fileName = startupProject.GetStartupFile(); - directory = startupProject.GetWorkingDirectory(); - } else { - var textView = CommonPackage.GetActiveTextView(serviceProvider); - if (textView == null) { - fileName = null; - directory = null; - return false; - } - fileName = textView.GetFilePath(); - directory = Path.GetDirectoryName(fileName); - } - return true; - } - - private static string remoteDebugProxyFolder = null; - - internal LanguagePreferences LangPrefs { - get { - return _langPrefs; - } - } - - public static string RemoteDebugProxyFolder { - get { - // Lazily evaluated - if (remoteDebugProxyFolder != null) { - return remoteDebugProxyFolder; - } - - const string ROOT_KEY = "Software\\Microsoft\\NodeJSTools\\" + AssemblyVersionInfo.VSVersion; - - // Try HKCU - try { - using (RegistryKey node = Registry.CurrentUser.OpenSubKey(ROOT_KEY)) { - if (node != null) { - remoteDebugProxyFolder = (string)node.GetValue("RemoteDebugProxyFolder"); - } - } - } catch (Exception) { - } - - // Try HKLM - if (remoteDebugProxyFolder == null) { - try { - using (RegistryKey node = Registry.LocalMachine.OpenSubKey(ROOT_KEY)) { - if (node != null) { - remoteDebugProxyFolder = (string)node.GetValue("RemoteDebugProxyFolder"); - } - } - } catch (Exception) { - } - } - - return remoteDebugProxyFolder; - } - } - - private IContentType ReplContentType { - get { - if (_contentType == null) { - _contentType = ComponentModel.GetService().GetContentType(NodejsConstants.Nodejs); - } - return _contentType; - } - } - - #endregion - - internal override VisualStudioTools.Navigation.LibraryManager CreateLibraryManager(CommonPackage package) { - return new NodejsLibraryManager(this); - } - - public override Type GetLibraryManagerType() { - return typeof(NodejsLibraryManager); - } - - public override bool IsRecognizedFile(string filename) { - var ext = Path.GetExtension(filename); - - return String.Equals(ext, NodejsConstants.JavaScriptExtension, StringComparison.OrdinalIgnoreCase); - } - - internal new object GetService(Type serviceType) { - return base.GetService(serviceType); - } - - public static string NodejsReferencePath { - get { - return Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "nodejsref.js" - ); - } - } - - public string BrowseForFileOpen(IntPtr owner, string filter, string initialPath = null) { - if (string.IsNullOrEmpty(initialPath)) { - initialPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + Path.DirectorySeparatorChar; - } - - IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; - if (null == uiShell) { - using (var sfd = new System.Windows.Forms.OpenFileDialog()) { - sfd.AutoUpgradeEnabled = true; - sfd.Filter = filter; - sfd.FileName = Path.GetFileName(initialPath); - sfd.InitialDirectory = Path.GetDirectoryName(initialPath); - DialogResult result; - if (owner == IntPtr.Zero) { - result = sfd.ShowDialog(); - } else { - result = sfd.ShowDialog(NativeWindow.FromHandle(owner)); - } - if (result == DialogResult.OK) { - return sfd.FileName; - } else { - return null; - } - } - } - - if (owner == IntPtr.Zero) { - ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out owner)); - } - - VSOPENFILENAMEW[] openInfo = new VSOPENFILENAMEW[1]; - openInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSOPENFILENAMEW)); - openInfo[0].pwzFilter = filter.Replace('|', '\0') + "\0"; - openInfo[0].hwndOwner = owner; - openInfo[0].nMaxFileName = 260; - var pFileName = Marshal.AllocCoTaskMem(520); - openInfo[0].pwzFileName = pFileName; - openInfo[0].pwzInitialDir = Path.GetDirectoryName(initialPath); - var nameArray = (Path.GetFileName(initialPath) + "\0").ToCharArray(); - Marshal.Copy(nameArray, 0, pFileName, nameArray.Length); - try { - int hr = uiShell.GetOpenFileNameViaDlg(openInfo); - if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) { - return null; - } - ErrorHandler.ThrowOnFailure(hr); - return Marshal.PtrToStringAuto(openInfo[0].pwzFileName); - } finally { - if (pFileName != IntPtr.Zero) { - Marshal.FreeCoTaskMem(pFileName); - } - } - } - - public string BrowseForFileSave(IntPtr owner, string filter, string initialPath = null) { - if (string.IsNullOrEmpty(initialPath)) { - initialPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + Path.DirectorySeparatorChar; - } - - IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; - if (null == uiShell) { - using (var sfd = new System.Windows.Forms.SaveFileDialog()) { - sfd.AutoUpgradeEnabled = true; - sfd.Filter = filter; - sfd.FileName = Path.GetFileName(initialPath); - sfd.InitialDirectory = Path.GetDirectoryName(initialPath); - DialogResult result; - if (owner == IntPtr.Zero) { - result = sfd.ShowDialog(); - } else { - result = sfd.ShowDialog(NativeWindow.FromHandle(owner)); - } - if (result == DialogResult.OK) { - return sfd.FileName; - } else { - return null; - } - } - } - - if (owner == IntPtr.Zero) { - ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out owner)); - } - - VSSAVEFILENAMEW[] saveInfo = new VSSAVEFILENAMEW[1]; - saveInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSSAVEFILENAMEW)); - saveInfo[0].pwzFilter = filter.Replace('|', '\0') + "\0"; - saveInfo[0].hwndOwner = owner; - saveInfo[0].nMaxFileName = 260; - var pFileName = Marshal.AllocCoTaskMem(520); - saveInfo[0].pwzFileName = pFileName; - saveInfo[0].pwzInitialDir = Path.GetDirectoryName(initialPath); - var nameArray = (Path.GetFileName(initialPath) + "\0").ToCharArray(); - Marshal.Copy(nameArray, 0, pFileName, nameArray.Length); - try { - int hr = uiShell.GetSaveFileNameViaDlg(saveInfo); - if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) { - return null; - } - ErrorHandler.ThrowOnFailure(hr); - return Marshal.PtrToStringAuto(saveInfo[0].pwzFileName); - } finally { - if (pFileName != IntPtr.Zero) { - Marshal.FreeCoTaskMem(pFileName); - } - } - } - - public string BrowseForDirectory(IntPtr owner, string initialDirectory = null) { - IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; - if (null == uiShell) { - using (var ofd = new FolderBrowserDialog()) { - ofd.RootFolder = Environment.SpecialFolder.Desktop; - ofd.ShowNewFolderButton = false; - DialogResult result; - if (owner == IntPtr.Zero) { - result = ofd.ShowDialog(); - } else { - result = ofd.ShowDialog(NativeWindow.FromHandle(owner)); - } - if (result == DialogResult.OK) { - return ofd.SelectedPath; - } else { - return null; - } - } - } - - if (owner == IntPtr.Zero) { - ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out owner)); - } - - VSBROWSEINFOW[] browseInfo = new VSBROWSEINFOW[1]; - browseInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSBROWSEINFOW)); - browseInfo[0].pwzInitialDir = initialDirectory; - browseInfo[0].hwndOwner = owner; - browseInfo[0].nMaxDirName = 260; - IntPtr pDirName = IntPtr.Zero; - try { - browseInfo[0].pwzDirName = pDirName = Marshal.AllocCoTaskMem(520); - int hr = uiShell.GetDirectoryViaBrowseDlg(browseInfo); - if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) { - return null; - } - ErrorHandler.ThrowOnFailure(hr); - return Marshal.PtrToStringAuto(browseInfo[0].pwzDirName); - } finally { - if (pDirName != IntPtr.Zero) { - Marshal.FreeCoTaskMem(pDirName); - } - } - } - - private void BrowseSurveyNewsOnIdle(object sender, ComponentManagerEventArgs e) { - this.OnIdle -= BrowseSurveyNewsOnIdle; - - lock (_surveyNewsUrlLock) { - if (!string.IsNullOrEmpty(_surveyNewsUrl)) { - OpenVsWebBrowser(this, _surveyNewsUrl); - _surveyNewsUrl = null; - } - } - } - - internal void BrowseSurveyNews(string url) { - lock (_surveyNewsUrlLock) { - _surveyNewsUrl = url; - } - - this.OnIdle += BrowseSurveyNewsOnIdle; - } - - private void CheckSurveyNewsThread(Uri url, bool warnIfNoneAvailable) { - // We can't use a simple WebRequest, because that doesn't have access - // to the browser's session cookies. Cookies are used to remember - // which survey/news item the user has submitted/accepted. The server - // checks the cookies and returns the survey/news urls that are - // currently available (availability is determined via the survey/news - // item start and end date). - var th = new Thread(() => { - var br = new WebBrowser(); - br.Tag = warnIfNoneAvailable; - br.DocumentCompleted += OnSurveyNewsDocumentCompleted; - br.Navigate(url); - Application.Run(); - }); - th.SetApartmentState(ApartmentState.STA); - th.Start(); - } - - private void OnSurveyNewsDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { - var br = (WebBrowser)sender; - var warnIfNoneAvailable = (bool)br.Tag; - if (br.Url == e.Url) { - List available = null; - - string json = br.DocumentText; - if (!string.IsNullOrEmpty(json)) { - int startIndex = json.IndexOf("
");
-                    if (startIndex > 0) {
-                        int endIndex = json.IndexOf("
", startIndex); - if (endIndex > 0) { - json = json.Substring(startIndex + 5, endIndex - startIndex - 5); - - try { - // Example JSON data returned by the server: - //{ - // "cannotvoteagain": [], - // "notvoted": [ - // "http://ptvs.azurewebsites.net/news/141", - // "http://ptvs.azurewebsites.net/news/41", - // ], - // "canvoteagain": [ - // "http://ptvs.azurewebsites.net/news/51" - // ] - //} - - // Description of each list: - // voted: cookie found - // notvoted: cookie not found - // canvoteagain: cookie found, but multiple votes are allowed - JavaScriptSerializer serializer = new JavaScriptSerializer(); - var results = serializer.Deserialize>>(json); - available = results["notvoted"]; - } catch (ArgumentException) { - } catch (InvalidOperationException) { - } - } - } - } - - if (available != null && available.Count > 0) { - BrowseSurveyNews(available[0]); - } else if (warnIfNoneAvailable) { - if (available != null) { - BrowseSurveyNews(GeneralOptionsPage.SurveyNewsIndexUrl); - } else { - BrowseSurveyNews(NodejsToolsInstallPath.GetFile("NoSurveyNewsFeed.html")); - } - } - - Application.ExitThread(); - } - } - - internal void CheckSurveyNews(bool forceCheckAndWarnIfNoneAvailable) { - bool shouldQueryServer = false; - if (forceCheckAndWarnIfNoneAvailable) { - shouldQueryServer = true; - } else { - shouldQueryServer = true; - var options = GeneralOptionsPage; - // Ensure that we don't prompt the user on their very first project creation. - // Delay by 3 days by pretending we checked 4 days ago (the default of check - // once a week ensures we'll check again in 3 days). - if (options.SurveyNewsLastCheck == DateTime.MinValue) { - options.SurveyNewsLastCheck = DateTime.Now - TimeSpan.FromDays(4); - options.SaveSettingsToStorage(); - } - - var elapsedTime = DateTime.Now - options.SurveyNewsLastCheck; - switch (options.SurveyNewsCheck) { - case SurveyNewsPolicy.Disabled: - break; - case SurveyNewsPolicy.CheckOnceDay: - shouldQueryServer = elapsedTime.TotalDays >= 1; - break; - case SurveyNewsPolicy.CheckOnceWeek: - shouldQueryServer = elapsedTime.TotalDays >= 7; - break; - case SurveyNewsPolicy.CheckOnceMonth: - shouldQueryServer = elapsedTime.TotalDays >= 30; - break; - default: - Debug.Assert(false, String.Format("Unexpected SurveyNewsPolicy: {0}.", options.SurveyNewsCheck)); - break; - } - } - - if (shouldQueryServer) { - var options = GeneralOptionsPage; - options.SurveyNewsLastCheck = DateTime.Now; - options.SaveSettingsToStorage(); - CheckSurveyNewsThread(new Uri(options.SurveyNewsFeedUrl), forceCheckAndWarnIfNoneAvailable); - } - } - - internal static void NavigateTo(string filename, int line, int col) { - VsUtilities.NavigateTo(Instance, filename, NodejsProjectNode.IsNodejsFile(filename) ? typeof(NodejsEditorFactory).GUID : Guid.Empty, line, col); - } - - internal static void NavigateTo(string filename, int pos) { - VsUtilities.NavigateTo(Instance, filename, NodejsProjectNode.IsNodejsFile(filename) ? typeof(NodejsEditorFactory).GUID : Guid.Empty, pos); - } - - /// - /// The analyzer which is used for loose files. - /// - internal VsProjectAnalyzer DefaultAnalyzer { - get { - if (_analyzer == null) { - _analyzer = new VsProjectAnalyzer(); - LogLooseFileAnalysisLevel(); - _analyzer.MaxLogLength = IntellisenseOptionsPage.AnalysisLogMax; - IntellisenseOptionsPage.AnalysisLevelChanged += IntellisenseOptionsPageAnalysisLevelChanged; - IntellisenseOptionsPage.SaveToDiskChanged += IntellisenseOptionsPageSaveToDiskChanged; - } - return _analyzer; - } - } - - private void IntellisenseOptionsPageSaveToDiskChanged(object sender, EventArgs e) { - _analyzer.SaveToDisk = IntellisenseOptionsPage.SaveToDisk; - } - - private void IntellisenseOptionsPageAnalysisLevelChanged(object sender, EventArgs e) { - var analyzer = new VsProjectAnalyzer(); - analyzer.SwitchAnalyzers(_analyzer); - if (_analyzer.RemoveUser()) { - _analyzer.Dispose(); - } - _analyzer = analyzer; - LogLooseFileAnalysisLevel(); - } - - private void LogLooseFileAnalysisLevel() { - var analyzer = _analyzer; - if(analyzer != null) - { - var val = analyzer.AnalysisLevel; - _logger.LogEvent(NodejsToolsLogEvent.AnalysisLevel, (int)val); - } - } - - -#if UNIT_TEST_INTEGRATION - // var testCase = require('./test/test-doubled.js'); for(var x in testCase) { console.log(x); } - public static string EvaluateJavaScript(string code) { - // TODO: Escaping code - string args = "-e \"" + code + "\""; - var psi = new ProcessStartInfo(NodePath, args); - psi.RedirectStandardOutput = true; - psi.RedirectStandardError = true; - var proc = Process.Start(psi); - var outpReceiver = new OutputReceiver(); - proc.OutputDataReceived += outpReceiver.DataRead; - proc.BeginErrorReadLine(); - proc.BeginOutputReadLine(); - - return outpReceiver._data.ToString(); - } - - private void GetTestCases(string module) { - var testCases = EvaluateJavaScript( - String.Format("var testCase = require('{0}'); for(var x in testCase) { console.log(x); }", module)); - foreach (var testCase in testCases.Split(new[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) { - } - } - - class OutputReceiver { - internal readonly StringBuilder _data = new StringBuilder(); - - public void DataRead(object sender, DataReceivedEventArgs e) { - if (e.Data != null) { - _data.Append(e.Data); - } - } - } -#endif - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading; +using System.Web.Script.Serialization; +using System.Windows.Forms; +using Microsoft.NodejsTools.Analysis; +using Microsoft.NodejsTools.Commands; +using Microsoft.NodejsTools.Debugger.DataTips; +using Microsoft.NodejsTools.Debugger.DebugEngine; +using Microsoft.NodejsTools.Debugger.Remote; +using Microsoft.NodejsTools.Intellisense; +using Microsoft.NodejsTools.Jade; +using Microsoft.NodejsTools.Logging; +using Microsoft.NodejsTools.Options; +using Microsoft.NodejsTools.Project; +using Microsoft.NodejsTools.ProjectWizard; +using Microsoft.NodejsTools.Repl; +using Microsoft.NodejsTools.Telemetry; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.ComponentModelHost; +using Microsoft.VisualStudio.Debugger.Interop; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudio.Utilities; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.Project; +using Microsoft.Win32; + +namespace Microsoft.NodejsTools { + /// + /// This is the class that implements the package exposed by this assembly. + /// + /// The minimum requirement for a class to be considered a valid package for Visual Studio + /// is to implement the IVsPackage interface and register itself with the shell. + /// This package uses the helper classes defined inside the Managed Package Framework (MPF) + /// to do it: it derives from the Package class that provides the implementation of the + /// IVsPackage interface and uses the registration attributes defined in the framework to + /// register itself and its components with the shell. + /// + // This attribute tells the PkgDef creation utility (CreatePkgDef.exe) that this class is + // a package. + [PackageRegistration(UseManagedResourcesOnly = true)] + // This attribute is used to register the information needed to show this package + // in the Help/About dialog of Visual Studio. + [InstalledProductRegistration("#110", "#112", AssemblyVersionInfo.Version, IconResourceID = 400)] + [Guid(Guids.NodejsPackageString)] + [ProvideOptionPage(typeof(NodejsGeneralOptionsPage), "Node.js Tools", "General", 114, 115, true)] + [ProvideOptionPage(typeof(NodejsNpmOptionsPage), "Node.js Tools", "Npm", 114, 116, true)] + [ProvideDebugEngine("Node.js Debugging", typeof(AD7ProgramProvider), typeof(AD7Engine), AD7Engine.DebugEngineId, setNextStatement: false, hitCountBp: true, justMyCodeStepping: false)] + [ProvideLanguageService(typeof(NodejsLanguageInfo), NodejsConstants.Nodejs, 106, RequestStockColors = true, ShowSmartIndent = true, ShowCompletion = true, DefaultToInsertSpaces = true, HideAdvancedMembersByDefault = true, EnableAdvancedMembersOption = true, ShowDropDownOptions = true)] + [ProvideDebugLanguage(NodejsConstants.Nodejs, Guids.NodejsDebugLanguageString, NodeExpressionEvaluatorGuid, AD7Engine.DebugEngineId)] + [WebSiteProject("JavaScript", "JavaScript")] + [ProvideProjectFactory(typeof(NodejsProjectFactory), null, null, null, null, ".\\NullPath", LanguageVsTemplate = NodejsConstants.JavaScript, SortPriority=0x17)] // outer flavor, no file extension + [ProvideDebugPortSupplier("Node remote debugging", typeof(NodeRemoteDebugPortSupplier), NodeRemoteDebugPortSupplier.PortSupplierId)] + [ProvideMenuResource(1000, 1)] // This attribute is needed to let the shell know that this package exposes some menus. + [ProvideBraceCompletion(NodejsConstants.Nodejs)] + [ProvideEditorExtension2(typeof(NodejsEditorFactory), NodeJsFileType, 50, "*:1", ProjectGuid = "{78D985FC-2CA0-4D08-9B6B-35ACD5E5294A}", NameResourceID = 102, DefaultName = "server", TemplateDir = ".\\NullPath")] + [ProvideEditorExtension2(typeof(NodejsEditorFactoryPromptForEncoding), NodeJsFileType, 50, "*:1", ProjectGuid = "{78D985FC-2CA0-4D08-9B6B-35ACD5E5294A}", NameResourceID = 113, DefaultName = "server")] + [ProvideEditorLogicalView(typeof(NodejsEditorFactory), VSConstants.LOGVIEWID.TextView_string)] + [ProvideEditorLogicalView(typeof(NodejsEditorFactoryPromptForEncoding), VSConstants.LOGVIEWID.TextView_string)] + [ProvideProjectItem(typeof(BaseNodeProjectFactory), NodejsConstants.Nodejs, "FileTemplates\\NewItem", 0)] + [ProvideLanguageTemplates("{349C5851-65DF-11DA-9384-00065B846F21}", NodejsConstants.JavaScript, Guids.NodejsPackageString, "Web", "Node.js Project Templates", "{" + Guids.NodejsBaseProjectFactoryString + "}", ".js", NodejsConstants.Nodejs, "{" + Guids.NodejsBaseProjectFactoryString + "}")] + [ProvideTextEditorAutomation(NodejsConstants.Nodejs, 106, 102, ProfileMigrationType.PassThrough)] + [ProvideLanguageService(typeof(JadeLanguageInfo), JadeContentTypeDefinition.JadeLanguageName, 3041, RequestStockColors = true, ShowSmartIndent = false, ShowCompletion = false, DefaultToInsertSpaces = true, HideAdvancedMembersByDefault = false, EnableAdvancedMembersOption = false, ShowDropDownOptions = false)] + [ProvideEditorExtension2(typeof(JadeEditorFactory), JadeContentTypeDefinition.JadeFileExtension, 50, __VSPHYSICALVIEWATTRIBUTES.PVA_SupportsPreview, "*:1", ProjectGuid = VSConstants.CLSID.MiscellaneousFilesProject_string, NameResourceID = 3041, EditorNameResourceId = 3045)] + [ProvideEditorLogicalView(typeof(JadeEditorFactory), VSConstants.LOGVIEWID.TextView_string)] + [ProvideLanguageExtension(typeof(JadeEditorFactory), JadeContentTypeDefinition.JadeFileExtension)] + [ProvideTextEditorAutomation(JadeContentTypeDefinition.JadeLanguageName, 3041, 3045, ProfileMigrationType.PassThrough)] + [ProvideLanguageEditorOptionPage(typeof(NodejsFormattingSpacingOptionsPage), NodejsConstants.Nodejs, "Formatting", "Spacing", "3042")] + [ProvideLanguageEditorOptionPage(typeof(NodejsFormattingBracesOptionsPage), NodejsConstants.Nodejs, "Formatting", "Braces", "3043")] + [ProvideLanguageEditorOptionPage(typeof(NodejsFormattingGeneralOptionsPage), NodejsConstants.Nodejs, "Formatting", "General", "3044")] + [ProvideLanguageEditorOptionPage(typeof(NodejsIntellisenseOptionsPage), NodejsConstants.Nodejs, "IntelliSense", "", "3048")] + [ProvideLanguageEditorOptionPage(typeof(NodejsAdvancedEditorOptionsPage), NodejsConstants.Nodejs, "Advanced", "", "3050")] + [ProvideCodeExpansions(Guids.NodejsLanguageInfoString, false, 106, "Nodejs", @"Snippets\%LCID%\SnippetsIndex.xml", @"Snippets\%LCID%\Nodejs\")] + [ProvideCodeExpansionPath("Nodejs", "Test", @"Snippets\%LCID%\Test\")] + internal sealed partial class NodejsPackage : CommonPackage { + internal const string NodeExpressionEvaluatorGuid = "{F16F2A71-1C45-4BAB-BECE-09D28CFDE3E6}"; + private IContentType _contentType; + internal const string NodeJsFileType = ".njs"; + internal static NodejsPackage Instance; + private string _surveyNewsUrl; + private object _surveyNewsUrlLock = new object(); + internal HashSet ChangedBuffers = new HashSet(); + private LanguagePreferences _langPrefs; + internal VsProjectAnalyzer _analyzer; + private NodejsToolsLogger _logger; + private ITelemetryLogger _telemetryLogger; + // Hold references for the subscribed events. Otherwise the callbacks will be garbage collected + // after the initialization + private List _subscribedCommandEvents = new List(); + + /// + /// Default constructor of the package. + /// Inside this method you can place any initialization code that does not require + /// any Visual Studio service because at this point the package object is created but + /// not sited yet inside Visual Studio environment. The place to do all the other + /// initialization is the Initialize method. + /// + public NodejsPackage() { + Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString())); + Debug.Assert(Instance == null, "NodejsPackage created multiple times"); + Instance = this; + } + + public NodejsGeneralOptionsPage GeneralOptionsPage { + get { + return (NodejsGeneralOptionsPage)GetDialogPage(typeof(NodejsGeneralOptionsPage)); + } + } + + public NodejsNpmOptionsPage NpmOptionsPage { + get { + return (NodejsNpmOptionsPage)GetDialogPage(typeof(NodejsNpmOptionsPage)); + } + } + + public NodejsFormattingSpacingOptionsPage FormattingSpacingOptionsPage { + get { + return (NodejsFormattingSpacingOptionsPage)GetDialogPage(typeof(NodejsFormattingSpacingOptionsPage)); + } + } + + public NodejsFormattingBracesOptionsPage FormattingBracesOptionsPage { + get { + return (NodejsFormattingBracesOptionsPage)GetDialogPage(typeof(NodejsFormattingBracesOptionsPage)); + } + } + + public NodejsFormattingGeneralOptionsPage FormattingGeneralOptionsPage { + get { + return (NodejsFormattingGeneralOptionsPage)GetDialogPage(typeof(NodejsFormattingGeneralOptionsPage)); + } + } + + public NodejsIntellisenseOptionsPage IntellisenseOptionsPage { + get { + return (NodejsIntellisenseOptionsPage)GetDialogPage(typeof(NodejsIntellisenseOptionsPage)); + } + } + + public NodejsAdvancedEditorOptionsPage AdvancedEditorOptionsPage { + get { + return (NodejsAdvancedEditorOptionsPage)GetDialogPage(typeof(NodejsAdvancedEditorOptionsPage)); + } + } + + public NodejsDiagnosticsOptionsPage DiagnosticsOptionsPage { + get { + return (NodejsDiagnosticsOptionsPage)GetDialogPage(typeof(NodejsDiagnosticsOptionsPage)); + } + } + + public EnvDTE.DTE DTE { + get { + return (EnvDTE.DTE)GetService(typeof(EnvDTE.DTE)); + } + } + + ///////////////////////////////////////////////////////////////////////////// + // Overridden Package Implementation + #region Package Members + + /// + /// Initialization of the package; this method is called right after the package is sited, so this is the place + /// where you can put all the initialization code that rely on services provided by VisualStudio. + /// + protected override void Initialize() { + Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString())); + base.Initialize(); + + SubscribeToVsCommandEvents( + (int)VSConstants.VSStd97CmdID.AddNewProject, + delegate { NewProjectFromExistingWizard.IsAddNewProjectCmd = true; }, + delegate { NewProjectFromExistingWizard.IsAddNewProjectCmd = false; } + ); + + var langService = new NodejsLanguageInfo(this); + ((IServiceContainer)this).AddService(langService.GetType(), langService, true); + + ((IServiceContainer)this).AddService(typeof(ClipboardServiceBase), new ClipboardService(), true); + + RegisterProjectFactory(new NodejsProjectFactory(this)); + RegisterEditorFactory(new NodejsEditorFactory(this)); + RegisterEditorFactory(new NodejsEditorFactoryPromptForEncoding(this)); + RegisterEditorFactory(new JadeEditorFactory(this)); + + // Add our command handlers for menu (commands must exist in the .vsct file) + var commands = new List { + new OpenReplWindowCommand(), + new OpenRemoteDebugProxyFolderCommand(), + new OpenRemoteDebugDocumentationCommand(), + new SurveyNewsCommand(), + new ImportWizardCommand(), + new DiagnosticsCommand(this) + }; + try { + commands.Add(new AzureExplorerAttachDebuggerCommand()); + } catch (NotSupportedException) { + } + RegisterCommands(commands, Guids.NodejsCmdSet); + + IVsTextManager textMgr = (IVsTextManager)Instance.GetService(typeof(SVsTextManager)); + + var langPrefs = new LANGPREFERENCES[1]; + langPrefs[0].guidLang = typeof(NodejsLanguageInfo).GUID; + ErrorHandler.ThrowOnFailure(textMgr.GetUserPreferences(null, null, langPrefs, null)); + _langPrefs = new LanguagePreferences(langPrefs[0]); + + var textManagerEvents2Guid = typeof(IVsTextManagerEvents2).GUID; + IConnectionPoint textManagerEvents2ConnectionPoint; + ((IConnectionPointContainer)textMgr).FindConnectionPoint(ref textManagerEvents2Guid, out textManagerEvents2ConnectionPoint); + uint cookie; + textManagerEvents2ConnectionPoint.Advise(_langPrefs, out cookie); + + var textManagerEventsGuid = typeof(IVsTextManagerEvents).GUID; + IConnectionPoint textManagerEventsConnectionPoint; + ((IConnectionPointContainer)textMgr).FindConnectionPoint(ref textManagerEventsGuid, out textManagerEventsConnectionPoint); + textManagerEventsConnectionPoint.Advise(new DataTipTextManagerEvents(this), out cookie); + + MakeDebuggerContextAvailable(); + + IntellisenseOptionsPage.AnalysisLogMaximumChanged += IntellisenseOptionsPage_AnalysisLogMaximumChanged; + + InitializeLogging(); + + InitializeTelemetry(); + + // The variable is inherited by child processes backing Test Explorer, and is used in + // the NTVS test discoverer and test executor to connect back to VS. + Environment.SetEnvironmentVariable(NodejsConstants.NodeToolsProcessIdEnvironmentVariable, Process.GetCurrentProcess().Id.ToString()); + } + + private void SubscribeToVsCommandEvents( + int eventId, + EnvDTE._dispCommandEvents_BeforeExecuteEventHandler beforeExecute = null, + EnvDTE._dispCommandEvents_AfterExecuteEventHandler afterExecute = null) { + var commandEventGuid = typeof(VSConstants.VSStd97CmdID).GUID.ToString("B"); + var targetEvent = DTE.Events.CommandEvents[commandEventGuid, eventId]; + if (beforeExecute != null) { + targetEvent.BeforeExecute += beforeExecute; + } + if (afterExecute != null) { + targetEvent.AfterExecute += afterExecute; + } + _subscribedCommandEvents.Add(targetEvent); + } + + + private void IntellisenseOptionsPage_AnalysisLogMaximumChanged(object sender, EventArgs e) { + if (_analyzer != null) { + _analyzer.MaxLogLength = IntellisenseOptionsPage.AnalysisLogMax; + } + } + + private void InitializeLogging() { + _logger = new NodejsToolsLogger(ComponentModel.GetExtensions().ToArray()); + + // log interesting stats on startup + _logger.LogEvent(NodejsToolsLogEvent.SurveyNewsFrequency, GeneralOptionsPage.SurveyNewsCheck); + _logger.LogEvent(NodejsToolsLogEvent.AnalysisLevel, IntellisenseOptionsPage.AnalysisLevel); + } + + private void InitializeTelemetry() { + var thisAssembly = typeof(NodejsPackage).Assembly; + + // Get telemetry logger + _telemetryLogger = TelemetrySetup.Instance.GetLogger(thisAssembly); + + TelemetrySetup.Instance.LogPackageLoad(_telemetryLogger, Guid.Parse(Guids.NodejsPackageString), thisAssembly, Application.ProductVersion); + } + + public new IComponentModel ComponentModel { + get { + return this.GetComponentModel(); + } + } + + internal NodejsToolsLogger Logger { + get { + return _logger; + } + } + + internal ITelemetryLogger TelemetryLogger { + get { + return _telemetryLogger; + } + } + + /// + /// Makes the debugger context available - this enables our debugger when we're installed into + /// a SKU which doesn't support every installed debugger. + /// + private void MakeDebuggerContextAvailable() { + var monitorSelection = (IVsMonitorSelection)GetService(typeof(SVsShellMonitorSelection)); + Guid debugEngineGuid = AD7Engine.DebugEngineGuid; + uint contextCookie; + if (ErrorHandler.Succeeded(monitorSelection.GetCmdUIContextCookie(ref debugEngineGuid, out contextCookie))) { + ErrorHandler.ThrowOnFailure(monitorSelection.SetCmdUIContext(contextCookie, 1)); + } + } + + internal IReplWindow2 OpenReplWindow(bool focus = true) { + var compModel = ComponentModel; + var provider = compModel.GetService(); + + var window = (IReplWindow2)provider.FindReplWindow(NodejsReplEvaluatorProvider.NodeReplId); + if (window == null) { + window = (IReplWindow2)provider.CreateReplWindow( + ReplContentType, + "Node.js Interactive Window", + typeof(NodejsLanguageInfo).GUID, + NodejsReplEvaluatorProvider.NodeReplId + ); + } + + IVsWindowFrame windowFrame = (IVsWindowFrame)((ToolWindowPane)window).Frame; + ErrorHandler.ThrowOnFailure(windowFrame.Show()); + + if (focus) { + window.Focus(); + } + + return window; + } + + internal static bool TryGetStartupFileAndDirectory(System.IServiceProvider serviceProvider, out string fileName, out string directory) { + var startupProject = GetStartupProject(serviceProvider); + if (startupProject != null) { + fileName = startupProject.GetStartupFile(); + directory = startupProject.GetWorkingDirectory(); + } else { + var textView = CommonPackage.GetActiveTextView(serviceProvider); + if (textView == null) { + fileName = null; + directory = null; + return false; + } + fileName = textView.GetFilePath(); + directory = Path.GetDirectoryName(fileName); + } + return true; + } + + private static string remoteDebugProxyFolder = null; + + internal LanguagePreferences LangPrefs { + get { + return _langPrefs; + } + } + + public static string RemoteDebugProxyFolder { + get { + // Lazily evaluated + if (remoteDebugProxyFolder != null) { + return remoteDebugProxyFolder; + } + + const string ROOT_KEY = "Software\\Microsoft\\NodeJSTools\\" + AssemblyVersionInfo.VSVersion; + + // Try HKCU + try { + using (RegistryKey node = Registry.CurrentUser.OpenSubKey(ROOT_KEY)) { + if (node != null) { + remoteDebugProxyFolder = (string)node.GetValue("RemoteDebugProxyFolder"); + } + } + } catch (Exception) { + } + + // Try HKLM + if (remoteDebugProxyFolder == null) { + try { + using (RegistryKey node = Registry.LocalMachine.OpenSubKey(ROOT_KEY)) { + if (node != null) { + remoteDebugProxyFolder = (string)node.GetValue("RemoteDebugProxyFolder"); + } + } + } catch (Exception) { + } + } + + return remoteDebugProxyFolder; + } + } + + private IContentType ReplContentType { + get { + if (_contentType == null) { + _contentType = ComponentModel.GetService().GetContentType(NodejsConstants.Nodejs); + } + return _contentType; + } + } + + #endregion + + internal override VisualStudioTools.Navigation.LibraryManager CreateLibraryManager(CommonPackage package) { + return new NodejsLibraryManager(this); + } + + public override Type GetLibraryManagerType() { + return typeof(NodejsLibraryManager); + } + + public override bool IsRecognizedFile(string filename) { + var ext = Path.GetExtension(filename); + + return String.Equals(ext, NodejsConstants.JavaScriptExtension, StringComparison.OrdinalIgnoreCase); + } + + internal new object GetService(Type serviceType) { + return base.GetService(serviceType); + } + + public static string NodejsReferencePath { + get { + return Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "nodejsref.js" + ); + } + } + + public string BrowseForFileOpen(IntPtr owner, string filter, string initialPath = null) { + if (string.IsNullOrEmpty(initialPath)) { + initialPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + Path.DirectorySeparatorChar; + } + + IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; + if (null == uiShell) { + using (var sfd = new System.Windows.Forms.OpenFileDialog()) { + sfd.AutoUpgradeEnabled = true; + sfd.Filter = filter; + sfd.FileName = Path.GetFileName(initialPath); + sfd.InitialDirectory = Path.GetDirectoryName(initialPath); + DialogResult result; + if (owner == IntPtr.Zero) { + result = sfd.ShowDialog(); + } else { + result = sfd.ShowDialog(NativeWindow.FromHandle(owner)); + } + if (result == DialogResult.OK) { + return sfd.FileName; + } else { + return null; + } + } + } + + if (owner == IntPtr.Zero) { + ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out owner)); + } + + VSOPENFILENAMEW[] openInfo = new VSOPENFILENAMEW[1]; + openInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSOPENFILENAMEW)); + openInfo[0].pwzFilter = filter.Replace('|', '\0') + "\0"; + openInfo[0].hwndOwner = owner; + openInfo[0].nMaxFileName = 260; + var pFileName = Marshal.AllocCoTaskMem(520); + openInfo[0].pwzFileName = pFileName; + openInfo[0].pwzInitialDir = Path.GetDirectoryName(initialPath); + var nameArray = (Path.GetFileName(initialPath) + "\0").ToCharArray(); + Marshal.Copy(nameArray, 0, pFileName, nameArray.Length); + try { + int hr = uiShell.GetOpenFileNameViaDlg(openInfo); + if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) { + return null; + } + ErrorHandler.ThrowOnFailure(hr); + return Marshal.PtrToStringAuto(openInfo[0].pwzFileName); + } finally { + if (pFileName != IntPtr.Zero) { + Marshal.FreeCoTaskMem(pFileName); + } + } + } + + public string BrowseForFileSave(IntPtr owner, string filter, string initialPath = null) { + if (string.IsNullOrEmpty(initialPath)) { + initialPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + Path.DirectorySeparatorChar; + } + + IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; + if (null == uiShell) { + using (var sfd = new System.Windows.Forms.SaveFileDialog()) { + sfd.AutoUpgradeEnabled = true; + sfd.Filter = filter; + sfd.FileName = Path.GetFileName(initialPath); + sfd.InitialDirectory = Path.GetDirectoryName(initialPath); + DialogResult result; + if (owner == IntPtr.Zero) { + result = sfd.ShowDialog(); + } else { + result = sfd.ShowDialog(NativeWindow.FromHandle(owner)); + } + if (result == DialogResult.OK) { + return sfd.FileName; + } else { + return null; + } + } + } + + if (owner == IntPtr.Zero) { + ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out owner)); + } + + VSSAVEFILENAMEW[] saveInfo = new VSSAVEFILENAMEW[1]; + saveInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSSAVEFILENAMEW)); + saveInfo[0].pwzFilter = filter.Replace('|', '\0') + "\0"; + saveInfo[0].hwndOwner = owner; + saveInfo[0].nMaxFileName = 260; + var pFileName = Marshal.AllocCoTaskMem(520); + saveInfo[0].pwzFileName = pFileName; + saveInfo[0].pwzInitialDir = Path.GetDirectoryName(initialPath); + var nameArray = (Path.GetFileName(initialPath) + "\0").ToCharArray(); + Marshal.Copy(nameArray, 0, pFileName, nameArray.Length); + try { + int hr = uiShell.GetSaveFileNameViaDlg(saveInfo); + if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) { + return null; + } + ErrorHandler.ThrowOnFailure(hr); + return Marshal.PtrToStringAuto(saveInfo[0].pwzFileName); + } finally { + if (pFileName != IntPtr.Zero) { + Marshal.FreeCoTaskMem(pFileName); + } + } + } + + public string BrowseForDirectory(IntPtr owner, string initialDirectory = null) { + IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; + if (null == uiShell) { + using (var ofd = new FolderBrowserDialog()) { + ofd.RootFolder = Environment.SpecialFolder.Desktop; + ofd.ShowNewFolderButton = false; + DialogResult result; + if (owner == IntPtr.Zero) { + result = ofd.ShowDialog(); + } else { + result = ofd.ShowDialog(NativeWindow.FromHandle(owner)); + } + if (result == DialogResult.OK) { + return ofd.SelectedPath; + } else { + return null; + } + } + } + + if (owner == IntPtr.Zero) { + ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out owner)); + } + + VSBROWSEINFOW[] browseInfo = new VSBROWSEINFOW[1]; + browseInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSBROWSEINFOW)); + browseInfo[0].pwzInitialDir = initialDirectory; + browseInfo[0].hwndOwner = owner; + browseInfo[0].nMaxDirName = 260; + IntPtr pDirName = IntPtr.Zero; + try { + browseInfo[0].pwzDirName = pDirName = Marshal.AllocCoTaskMem(520); + int hr = uiShell.GetDirectoryViaBrowseDlg(browseInfo); + if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) { + return null; + } + ErrorHandler.ThrowOnFailure(hr); + return Marshal.PtrToStringAuto(browseInfo[0].pwzDirName); + } finally { + if (pDirName != IntPtr.Zero) { + Marshal.FreeCoTaskMem(pDirName); + } + } + } + + private void BrowseSurveyNewsOnIdle(object sender, ComponentManagerEventArgs e) { + this.OnIdle -= BrowseSurveyNewsOnIdle; + + lock (_surveyNewsUrlLock) { + if (!string.IsNullOrEmpty(_surveyNewsUrl)) { + OpenVsWebBrowser(this, _surveyNewsUrl); + _surveyNewsUrl = null; + } + } + } + + internal void BrowseSurveyNews(string url) { + lock (_surveyNewsUrlLock) { + _surveyNewsUrl = url; + } + + this.OnIdle += BrowseSurveyNewsOnIdle; + } + + private void CheckSurveyNewsThread(Uri url, bool warnIfNoneAvailable) { + // We can't use a simple WebRequest, because that doesn't have access + // to the browser's session cookies. Cookies are used to remember + // which survey/news item the user has submitted/accepted. The server + // checks the cookies and returns the survey/news urls that are + // currently available (availability is determined via the survey/news + // item start and end date). + var th = new Thread(() => { + var br = new WebBrowser(); + br.Tag = warnIfNoneAvailable; + br.DocumentCompleted += OnSurveyNewsDocumentCompleted; + br.Navigate(url); + Application.Run(); + }); + th.SetApartmentState(ApartmentState.STA); + th.Start(); + } + + private void OnSurveyNewsDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) { + var br = (WebBrowser)sender; + var warnIfNoneAvailable = (bool)br.Tag; + if (br.Url == e.Url) { + List available = null; + + string json = br.DocumentText; + if (!string.IsNullOrEmpty(json)) { + int startIndex = json.IndexOf("
");
+                    if (startIndex > 0) {
+                        int endIndex = json.IndexOf("
", startIndex); + if (endIndex > 0) { + json = json.Substring(startIndex + 5, endIndex - startIndex - 5); + + try { + // Example JSON data returned by the server: + //{ + // "cannotvoteagain": [], + // "notvoted": [ + // "http://ptvs.azurewebsites.net/news/141", + // "http://ptvs.azurewebsites.net/news/41", + // ], + // "canvoteagain": [ + // "http://ptvs.azurewebsites.net/news/51" + // ] + //} + + // Description of each list: + // voted: cookie found + // notvoted: cookie not found + // canvoteagain: cookie found, but multiple votes are allowed + JavaScriptSerializer serializer = new JavaScriptSerializer(); + var results = serializer.Deserialize>>(json); + available = results["notvoted"]; + } catch (ArgumentException) { + } catch (InvalidOperationException) { + } + } + } + } + + if (available != null && available.Count > 0) { + BrowseSurveyNews(available[0]); + } else if (warnIfNoneAvailable) { + if (available != null) { + BrowseSurveyNews(GeneralOptionsPage.SurveyNewsIndexUrl); + } else { + BrowseSurveyNews(NodejsToolsInstallPath.GetFile("NoSurveyNewsFeed.html")); + } + } + + Application.ExitThread(); + } + } + + internal void CheckSurveyNews(bool forceCheckAndWarnIfNoneAvailable) { + bool shouldQueryServer = false; + if (forceCheckAndWarnIfNoneAvailable) { + shouldQueryServer = true; + } else { + shouldQueryServer = true; + var options = GeneralOptionsPage; + // Ensure that we don't prompt the user on their very first project creation. + // Delay by 3 days by pretending we checked 4 days ago (the default of check + // once a week ensures we'll check again in 3 days). + if (options.SurveyNewsLastCheck == DateTime.MinValue) { + options.SurveyNewsLastCheck = DateTime.Now - TimeSpan.FromDays(4); + options.SaveSettingsToStorage(); + } + + var elapsedTime = DateTime.Now - options.SurveyNewsLastCheck; + switch (options.SurveyNewsCheck) { + case SurveyNewsPolicy.Disabled: + break; + case SurveyNewsPolicy.CheckOnceDay: + shouldQueryServer = elapsedTime.TotalDays >= 1; + break; + case SurveyNewsPolicy.CheckOnceWeek: + shouldQueryServer = elapsedTime.TotalDays >= 7; + break; + case SurveyNewsPolicy.CheckOnceMonth: + shouldQueryServer = elapsedTime.TotalDays >= 30; + break; + default: + Debug.Assert(false, String.Format("Unexpected SurveyNewsPolicy: {0}.", options.SurveyNewsCheck)); + break; + } + } + + if (shouldQueryServer) { + var options = GeneralOptionsPage; + options.SurveyNewsLastCheck = DateTime.Now; + options.SaveSettingsToStorage(); + CheckSurveyNewsThread(new Uri(options.SurveyNewsFeedUrl), forceCheckAndWarnIfNoneAvailable); + } + } + + internal static void NavigateTo(string filename, int line, int col) { + VsUtilities.NavigateTo(Instance, filename, NodejsProjectNode.IsNodejsFile(filename) ? typeof(NodejsEditorFactory).GUID : Guid.Empty, line, col); + } + + internal static void NavigateTo(string filename, int pos) { + VsUtilities.NavigateTo(Instance, filename, NodejsProjectNode.IsNodejsFile(filename) ? typeof(NodejsEditorFactory).GUID : Guid.Empty, pos); + } + + /// + /// The analyzer which is used for loose files. + /// + internal VsProjectAnalyzer DefaultAnalyzer { + get { + if (_analyzer == null) { + _analyzer = new VsProjectAnalyzer(); + LogLooseFileAnalysisLevel(); + _analyzer.MaxLogLength = IntellisenseOptionsPage.AnalysisLogMax; + IntellisenseOptionsPage.AnalysisLevelChanged += IntellisenseOptionsPageAnalysisLevelChanged; + IntellisenseOptionsPage.SaveToDiskChanged += IntellisenseOptionsPageSaveToDiskChanged; + } + return _analyzer; + } + } + + private void IntellisenseOptionsPageSaveToDiskChanged(object sender, EventArgs e) { + _analyzer.SaveToDisk = IntellisenseOptionsPage.SaveToDisk; + } + + private void IntellisenseOptionsPageAnalysisLevelChanged(object sender, EventArgs e) { + var analyzer = new VsProjectAnalyzer(); + analyzer.SwitchAnalyzers(_analyzer); + if (_analyzer.RemoveUser()) { + _analyzer.Dispose(); + } + _analyzer = analyzer; + LogLooseFileAnalysisLevel(); + } + + private void LogLooseFileAnalysisLevel() { + var analyzer = _analyzer; + if(analyzer != null) + { + var val = analyzer.AnalysisLevel; + _logger.LogEvent(NodejsToolsLogEvent.AnalysisLevel, (int)val); + } + } + + +#if UNIT_TEST_INTEGRATION + // var testCase = require('./test/test-doubled.js'); for(var x in testCase) { console.log(x); } + public static string EvaluateJavaScript(string code) { + // TODO: Escaping code + string args = "-e \"" + code + "\""; + var psi = new ProcessStartInfo(NodePath, args); + psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; + var proc = Process.Start(psi); + var outpReceiver = new OutputReceiver(); + proc.OutputDataReceived += outpReceiver.DataRead; + proc.BeginErrorReadLine(); + proc.BeginOutputReadLine(); + + return outpReceiver._data.ToString(); + } + + private void GetTestCases(string module) { + var testCases = EvaluateJavaScript( + String.Format("var testCase = require('{0}'); for(var x in testCase) { console.log(x); }", module)); + foreach (var testCase in testCases.Split(new[] { "\r", "\n", "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) { + } + } + + class OutputReceiver { + internal readonly StringBuilder _data = new StringBuilder(); + + public void DataRead(object sender, DataReceivedEventArgs e) { + if (e.Data != null) { + _data.Append(e.Data); + } + } + } +#endif + } +} diff --git a/Nodejs/Product/Nodejs/NodejsProject.cs b/Nodejs/Product/Nodejs/NodejsProject.cs index 5b467b801..38dd482ce 100644 --- a/Nodejs/Product/Nodejs/NodejsProject.cs +++ b/Nodejs/Product/Nodejs/NodejsProject.cs @@ -1,1014 +1,1014 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.ComponentModel.Design; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Runtime.InteropServices; -using System.Text; -using System.Xml; -using Microsoft.NodejsTools.Project; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Azure; -using Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Flavor; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.Project; - -namespace Microsoft.NodejsTools { - [Guid("78D985FC-2CA0-4D08-9B6B-35ACD5E5294A")] - class NodejsProject : FlavoredProjectBase, IOleCommandTarget, IVsProjectFlavorCfgProvider, IVsProject, IVsProject2, IAzureRoleProject { - internal IVsProject _innerProject; - internal IVsProject3 _innerProject3; - internal NodejsPackage _package; - private OleMenuCommandService _menuService; - private List _commands = new List(); - private IVsProjectFlavorCfgProvider _innerVsProjectFlavorCfgProvider; - - protected override void Close() { - if (_menuService != null) { - foreach (var command in _commands) { - _menuService.RemoveCommand(command); - } - _menuService.Dispose(); - } - _commands.Clear(); - base.Close(); - } - - protected override void InitializeForOuter(string fileName, string location, string name, uint flags, ref Guid guidProject, out bool cancel) { - CommandID menuCommandID = new CommandID(VSConstants.GUID_VSStandardCommandSet97, (int)VSConstants.VSStd97CmdID.Open); - OleMenuCommand menuItem = new OleMenuCommand(OpenFile, null, OpenFileBeforeQueryStatus, menuCommandID); - AddCommand(menuItem); - - menuCommandID = new CommandID(VSConstants.GUID_VSStandardCommandSet97, (int)VSConstants.VSStd97CmdID.ViewCode); - menuItem = new OleMenuCommand(OpenFile, null, OpenFileBeforeQueryStatus, menuCommandID); - AddCommand(menuItem); - - menuCommandID = new CommandID(VSConstants.VSStd2K, (int)VSConstants.VSStd2KCmdID.ECMD_VIEWMARKUP); - menuItem = new OleMenuCommand(OpenFile, null, OpenFileBeforeQueryStatus, menuCommandID); - AddCommand(menuItem); - - base.InitializeForOuter(fileName, location, name, flags, ref guidProject, out cancel); - - object extObject; - ErrorHandler.ThrowOnFailure( - _innerVsHierarchy.GetProperty( - VSConstants.VSITEMID_ROOT, - (int)__VSHPROPID.VSHPROPID_ExtObject, - out extObject - ) - ); - - var proj = extObject as EnvDTE.Project; - if (proj != null) { - try { - object webAppExtender = proj.get_Extender("WebApplication"); - if (webAppExtender != null && webAppExtender is WebAppExtenderFilter) { - ((dynamic)((WebAppExtenderFilter)webAppExtender).InnerObject).StartWebServerOnDebug = false; - } - } catch (COMException) { - // extender doesn't exist... - } - } - } - - private void AddCommand(OleMenuCommand menuItem) { - _menuService.AddCommand(menuItem); - _commands.Add(menuItem); - } - - private void OpenFile(object sender, EventArgs e) { - var oleMenu = sender as OleMenuCommand; - oleMenu.Supported = false; - - foreach (var vsItemSelection in GetSelectedItems()) { - if (IsJavaScriptFile(Name(vsItemSelection))) { - ErrorHandler.ThrowOnFailure(OpenWithNodejsEditor(vsItemSelection.itemid)); - } else { - ErrorHandler.ThrowOnFailure(OpenWithDefaultEditor(vsItemSelection.itemid)); - } - } - } - - private void OpenFileBeforeQueryStatus(object sender, EventArgs e) { - var oleMenu = sender as OleMenuCommand; - oleMenu.Supported = false; - - foreach (var vsItemSelection in GetSelectedItems()) { - object name; - ErrorHandler.ThrowOnFailure(vsItemSelection.pHier.GetProperty(vsItemSelection.itemid, (int)__VSHPROPID.VSHPROPID_Name, out name)); - - if (IsJavaScriptFile(Name(vsItemSelection))) { - oleMenu.Supported = true; - } - } - } - - internal static string Name(VSITEMSELECTION item) { - return GetItemName(item.pHier, item.itemid); - } - - internal static string GetItemName(IVsHierarchy hier, uint itemid) { - object name; - ErrorHandler.ThrowOnFailure(hier.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_Name, out name)); - return (string)name; - } - - private int OpenWithDefaultEditor(uint selectionItemId) { - Guid view = Guid.Empty; - IVsWindowFrame frame; - int hr = ((IVsProject)_innerVsHierarchy).OpenItem( - selectionItemId, - ref view, - IntPtr.Zero, - out frame - ); - if (ErrorHandler.Succeeded(hr)) { - hr = frame.Show(); - } - return hr; - } - - protected override void SetInnerProject(IntPtr innerIUnknown) { - var inner = Marshal.GetObjectForIUnknown(innerIUnknown); - - // The reason why we keep a reference to those is that doing a QI after being - // aggregated would do the AddRef on the outer object. - _innerProject = inner as IVsProject; - _innerProject3 = inner as IVsProject3; - _innerVsHierarchy = inner as IVsHierarchy; - _innerVsProjectFlavorCfgProvider = inner as IVsProjectFlavorCfgProvider; - - // Ensure we have a service provider as this is required for menu items to work - if (this.serviceProvider == null) - this.serviceProvider = (System.IServiceProvider)this._package; - - // Now let the base implementation set the inner object - base.SetInnerProject(innerIUnknown); - - // Add our commands (this must run after we called base.SetInnerProject) - _menuService = ((System.IServiceProvider)this).GetService(typeof(IMenuCommandService)) as OleMenuCommandService; - - } - - private bool TryHandleRightClick(IntPtr pvaIn, out int res) { - Guid itemType = GetSelectedItemType(); - - if (TryShowContextMenu(pvaIn, itemType, out res)) { - return true; - } - - return false; - } - - /// - /// Gets all of the currently selected items. - /// - /// - private IEnumerable GetSelectedItems() { - IVsMonitorSelection monitorSelection = _package.GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection; - - IntPtr hierarchyPtr = IntPtr.Zero; - IntPtr selectionContainer = IntPtr.Zero; - try { - uint selectionItemId; - IVsMultiItemSelect multiItemSelect = null; - ErrorHandler.ThrowOnFailure(monitorSelection.GetCurrentSelection(out hierarchyPtr, out selectionItemId, out multiItemSelect, out selectionContainer)); - - if (selectionItemId != VSConstants.VSITEMID_NIL && hierarchyPtr != IntPtr.Zero) { - IVsHierarchy hierarchy = Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy; - - if (selectionItemId != VSConstants.VSITEMID_SELECTION) { - // This is a single selection. Compare hirarchy with our hierarchy and get node from itemid - if (Utilities.IsSameComObject(this, hierarchy)) { - yield return new VSITEMSELECTION() { itemid = selectionItemId, pHier = hierarchy }; - } - } else if (multiItemSelect != null) { - // This is a multiple item selection. - // Get number of items selected and also determine if the items are located in more than one hierarchy - - uint numberOfSelectedItems; - int isSingleHierarchyInt; - ErrorHandler.ThrowOnFailure(multiItemSelect.GetSelectionInfo(out numberOfSelectedItems, out isSingleHierarchyInt)); - bool isSingleHierarchy = (isSingleHierarchyInt != 0); - - // Now loop all selected items and add to the list only those that are selected within this hierarchy - if (!isSingleHierarchy || (isSingleHierarchy && Utilities.IsSameComObject(this, hierarchy))) { - Debug.Assert(numberOfSelectedItems > 0, "Bad number of selected itemd"); - VSITEMSELECTION[] vsItemSelections = new VSITEMSELECTION[numberOfSelectedItems]; - uint flags = (isSingleHierarchy) ? (uint)__VSGSIFLAGS.GSI_fOmitHierPtrs : 0; - ErrorHandler.ThrowOnFailure(multiItemSelect.GetSelectedItems(flags, numberOfSelectedItems, vsItemSelections)); - - foreach (VSITEMSELECTION vsItemSelection in vsItemSelections) { - yield return new VSITEMSELECTION() { itemid = vsItemSelection.itemid, pHier = hierarchy }; - } - } - } - } - } finally { - if (hierarchyPtr != IntPtr.Zero) { - Marshal.Release(hierarchyPtr); - } - if (selectionContainer != IntPtr.Zero) { - Marshal.Release(selectionContainer); - } - } - } - - private Guid GetSelectedItemType() { - Guid itemType = Guid.Empty; - foreach (var vsItemSelection in GetSelectedItems()) { - Guid typeGuid = GetItemType(vsItemSelection); - - if (itemType == Guid.Empty) { - itemType = typeGuid; - } else if (itemType != typeGuid) { - // we have multiple item types - itemType = Guid.Empty; - break; - } - } - return itemType; - } - - private bool TryShowContextMenu(IntPtr pvaIn, Guid itemType, out int res) { - if (itemType == new Guid(Guids.NodejsProjectFactoryString)) { - // multiple Node prjoect nodes selected - res = ShowContextMenu(pvaIn, VsMenus.IDM_VS_CTXT_PROJNODE/*IDM_VS_CTXT_WEBPROJECT*/); - return true; - } else if (itemType == VSConstants.GUID_ItemType_PhysicalFile) { - // multiple files selected - res = ShowContextMenu(pvaIn, VsMenus.IDM_VS_CTXT_ITEMNODE); - return true; - } else if (itemType == VSConstants.GUID_ItemType_PhysicalFolder) { - res = ShowContextMenu(pvaIn, VsMenus.IDM_VS_CTXT_FOLDERNODE); - return true; - } - res = VSConstants.E_FAIL; - return false; - } - - private int ShowContextMenu(IntPtr pvaIn, int ctxMenu) { - object variant = Marshal.GetObjectForNativeVariant(pvaIn); - UInt32 pointsAsUint = (UInt32)variant; - short x = (short)(pointsAsUint & 0x0000ffff); - short y = (short)((pointsAsUint & 0xffff0000) / 0x10000); - - POINTS points = new POINTS(); - points.x = x; - points.y = y; - - return ShowContextMenu(ctxMenu, VsMenus.guidSHLMainMenu, points); - } - - /// - /// Shows the specified context menu at a specified location. - /// - /// The context menu ID. - /// The GUID of the menu group. - /// The location at which to show the menu. - internal int ShowContextMenu(int menuId, Guid menuGroup, POINTS points) { - IVsUIShell shell = _package.GetService(typeof(SVsUIShell)) as IVsUIShell; - - Debug.Assert(shell != null, "Could not get the ui shell from the project"); - if (shell == null) { - return VSConstants.E_FAIL; - } - POINTS[] pnts = new POINTS[1]; - pnts[0].x = points.x; - pnts[0].y = points.y; - return shell.ShowContextMenu(0, ref menuGroup, menuId, pnts, (Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget)this); - } - - protected override int ExecCommand(uint itemid, ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { - if (pguidCmdGroup == VsMenus.guidVsUIHierarchyWindowCmds) { - switch ((VSConstants.VsUIHierarchyWindowCmdIds)nCmdID) { - case VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_RightClick: - int res; - if (TryHandleRightClick(pvaIn, out res)) { - return res; - } - break; - case VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_DoubleClick: - case VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_EnterKey: - // open the document if it's an JavaScript file - if (IsJavaScriptFile(_innerVsHierarchy, itemid)) { - int hr = OpenWithNodejsEditor(itemid); - - if (ErrorHandler.Succeeded(hr)) { - return hr; - } - } - break; - - } - } - - var result = base.ExecCommand(itemid, ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); - return result; - } - - int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { - return ((IOleCommandTarget)_menuService).Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); - } - - private bool IsJavaScriptFile(IVsHierarchy iVsHierarchy, uint itemid) { - object name; - ErrorHandler.ThrowOnFailure(iVsHierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_Name, out name)); - - return IsJavaScriptFile(name); - } - - private static bool IsJavaScriptFile(object name) { - string strName = name as string; - if (strName != null) { - var ext = Path.GetExtension(strName); - if (String.Equals(ext, ".js", StringComparison.OrdinalIgnoreCase)) { - return true; - } - } - return false; - } - - int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { - if (pguidCmdGroup == Guids.Eureka) { - for (int i = 0; i < prgCmds.Length; i++) { - switch (prgCmds[i].cmdID) { - case 0x102: // View in Web Page Inspector from Eureka web tools - prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_ENABLED); - return VSConstants.S_OK; - } - } - } else if (pguidCmdGroup == Guids.VenusCommandId) { - for (int i = 0; i < prgCmds.Length; i++) { - switch (prgCmds[i].cmdID) { - case 0x034: /* add app assembly folder */ - case 0x035: /* add app code folder */ - case 0x036: /* add global resources */ - case 0x037: /* add local resources */ - case 0x038: /* add web refs folder */ - case 0x039: /* add data folder */ - case 0x040: /* add browser folders */ - case 0x041: /* theme */ - case 0x054: /* package settings */ - case 0x055: /* context package settings */ - - prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_ENABLED); - return VSConstants.S_OK; - } - } - } else if (pguidCmdGroup == Guids.WebPackageCommandId) { - if (prgCmds[0].cmdID == 0x101 /* EnablePublishToWindowsAzureMenuItem*/) { - } - } else if (pguidCmdGroup == Guids.WebAppCmdId) { - for (int i = 0; i < prgCmds.Length; i++) { - switch (prgCmds[i].cmdID) { - case 0x06A: /* check accessibility */ - prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_DEFHIDEONCTXTMENU | OLECMDF.OLECMDF_ENABLED); - return VSConstants.S_OK; - } - } - } else if (pguidCmdGroup == VSConstants.VSStd2K) { - for (int i = 0; i < prgCmds.Length; i++) { - switch ((VSConstants.VSStd2KCmdID)prgCmds[i].cmdID) { - case VSConstants.VSStd2KCmdID.SETASSTARTPAGE: - case VSConstants.VSStd2KCmdID.CHECK_ACCESSIBILITY: - prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_DEFHIDEONCTXTMENU | OLECMDF.OLECMDF_ENABLED); - return VSConstants.S_OK; - } - } - } else if (pguidCmdGroup == VSConstants.GUID_VSStandardCommandSet97) { - for (int i = 0; i < prgCmds.Length; i++) { - switch ((VSConstants.VSStd97CmdID)prgCmds[i].cmdID) { - case VSConstants.VSStd97CmdID.PreviewInBrowser: - case VSConstants.VSStd97CmdID.BrowseWith: - prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_DEFHIDEONCTXTMENU | OLECMDF.OLECMDF_ENABLED); - return VSConstants.S_OK; - } - } - } - - return ((IOleCommandTarget)_menuService).QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); - } - - - #region IVsProjectFlavorCfgProvider Members - - public int CreateProjectFlavorCfg(IVsCfg pBaseProjectCfg, out IVsProjectFlavorCfg ppFlavorCfg) { - // We're flavored with a Web Application project and our normal project... But we don't - // want the web application project to influence our config as that alters our debug - // launch story. We control that w/ the Django project which is actually just letting the - // base Node.js project handle it. So we keep the base Node.js project config here. - IVsProjectFlavorCfg webCfg; - ErrorHandler.ThrowOnFailure( - _innerVsProjectFlavorCfgProvider.CreateProjectFlavorCfg( - pBaseProjectCfg, - out webCfg - ) - ); - ppFlavorCfg = new NodejsProjectConfig(pBaseProjectCfg, webCfg); - - return VSConstants.S_OK; - } - - #endregion - - - protected override int GetProperty(uint itemId, int propId, out object property) { - switch ((__VSHPROPID)propId) { - case __VSHPROPID.VSHPROPID_IconIndex: - case __VSHPROPID.VSHPROPID_OpenFolderIconIndex: - // Venus wants to change the icon for special folders using the IconIndex. All of our - // folders respond to IconHandles so we just force folders down that code path rather - // than trying to hand out the correct IconIndex here - if (GetItemType(new VSITEMSELECTION() { itemid = itemId, pHier = this }) == VSConstants.GUID_ItemType_PhysicalFolder) { - property = null; - return VSConstants.DISP_E_MEMBERNOTFOUND; - } - break; - } - switch ((__VSHPROPID4)propId) { - - case __VSHPROPID4.VSHPROPID_TargetFrameworkMoniker: - // really only here for testing so WAP projects load correctly... - // But this also impacts the toolbox by filtering what available items there are. - property = ".NETFramework,Version=v4.5,Profile=Client"; - return VSConstants.S_OK; - } - switch ((__VSHPROPID2)propId) { - case __VSHPROPID2.VSHPROPID_CfgPropertyPagesCLSIDList: { - var res = base.GetProperty(itemId, propId, out property); - property = RemovePropertyPagesFromList((string)property, CfgSpecificPropertyPagesToRemove); - return res; - } - case __VSHPROPID2.VSHPROPID_PropertyPagesCLSIDList: { - var res = base.GetProperty(itemId, propId, out property); - property = RemovePropertyPagesFromList((string)property, PropertyPagesToRemove); - return res; - } - } -#if DEV14_OR_LATER - switch((__VSHPROPID8)propId) { - case __VSHPROPID8.VSHPROPID_SupportsIconMonikers: - property = true; - return VSConstants.S_OK; - } -#endif - - return base.GetProperty(itemId, propId, out property); - } - - internal static string[] CfgSpecificPropertyPagesToRemove = new[] { - "{A553AD0B-2F9E-4BCE-95B3-9A1F7074BC27}", // Package/Publish Web - "{9AB2347D-948D-4CD2-8DBE-F15F0EF78ED3}", // Package/Publish SQL - }; - - internal static string[] PropertyPagesToRemove = new[] { - "{8C0201FE-8ECA-403C-92A3-1BC55F031979}", // typeof(DeployPropertyPageComClass) - "{ED3B544C-26D8-4348-877B-A1F7BD505ED9}", // typeof(DatabaseDeployPropertyPageComClass) - "{909D16B3-C8E8-43D1-A2B8-26EA0D4B6B57}", // Microsoft.VisualStudio.Web.Application.WebPropertyPage - "{379354F2-BBB3-4BA9-AA71-FBE7B0E5EA94}" // Microsoft.VisualStudio.Web.Application.SilverlightLinksPage - }; - - internal string RemovePropertyPagesFromList(string propertyPagesList, string[] pagesToRemove) { - if (pagesToRemove != null) { - propertyPagesList = propertyPagesList.ToUpper(CultureInfo.InvariantCulture); - foreach (string s in pagesToRemove) { - int index = propertyPagesList.IndexOf(s, StringComparison.Ordinal); - if (index != -1) { - // Guids are separated by ';' so if we remove the last one also remove the last ';' - int index2 = index + s.Length + 1; - if (index2 >= propertyPagesList.Length) - propertyPagesList = propertyPagesList.Substring(0, index).TrimEnd(';'); - else - propertyPagesList = propertyPagesList.Substring(0, index) + propertyPagesList.Substring(index2); - } - } - } - return propertyPagesList; - } - - internal static Guid GetItemType(VSITEMSELECTION vsItemSelection) { - Guid typeGuid; - try { - ErrorHandler.ThrowOnFailure( - vsItemSelection.pHier.GetGuidProperty( - vsItemSelection.itemid, - (int)__VSHPROPID.VSHPROPID_TypeGuid, - out typeGuid - ) - ); - } catch (System.Runtime.InteropServices.COMException) { - return Guid.Empty; - } - return typeGuid; - } - - private static EnvDTE.ProjectItem GetExtensionObject(IVsHierarchy hierarchy, uint itemId) { - object project; - - ErrorHandler.ThrowOnFailure( - hierarchy.GetProperty( - itemId, - (int)__VSHPROPID.VSHPROPID_ExtObject, - out project - ) - ); - - return (project as EnvDTE.ProjectItem); - } - - private int OpenWithNodejsEditor(uint selectionItemId) { - // If the item type of this file is not compile, we don't actually want to open with Nodejs and should instead use the default. - Guid ourEditor; - var properties = GetExtensionObject(_innerVsHierarchy, selectionItemId).Properties; - try { - var itemType = properties.Item("ItemType").Value; - ourEditor = (string)itemType == ProjectFileConstants.Compile && !IsES6IntellisensePreview() ? Guids.NodejsEditorFactory : Guid.Empty; - } catch (ArgumentException) { - // no item type, file is excluded from project. - ourEditor = Guids.NodejsEditorFactory; - } - - Guid view = Guid.Empty; - IVsWindowFrame frame; - - // DOCDATAEXISTING_UNKNOWN http://msdn.microsoft.com/en-us/library/vstudio/bb139396(v=vs.110).aspx - // Force OpenStandardEditor to lookup if the document is currently open or not, and if it is. If it's - // open in a different editor the user will be prompted to close it. - var docDataExistingUnknown = new IntPtr(-1); - int hr = ((IVsProject3)_innerVsHierarchy).OpenItemWithSpecific( - selectionItemId, - 0, - ref ourEditor, - null, - ref view, - docDataExistingUnknown, - out frame - ); - if (frame != null && ErrorHandler.Succeeded(hr)) { - hr = frame.Show(); - } - return hr; - } - - private bool IsES6IntellisensePreview() { - // If the analysis level is set to preview then use the TypeScript language service for node.js - return NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevel == Options.AnalysisLevel.Preview; - } - - #region IVsProject Members - - public int AddItem(uint itemidLoc, VSADDITEMOPERATION dwAddItemOperation, string pszItemName, uint cFilesToOpen, string[] rgpszFilesToOpen, IntPtr hwndDlgOwner, VSADDRESULT[] pResult) { - // Check if we are adding an item to a folder that consists of browser-side code. - // In this case, we will want to open the file with the default editor. - var isClientCode = false; - var project = _innerVsHierarchy.GetProject().GetNodejsProject(); - - var selectedItems = this.GetSelectedItems().GetEnumerator(); - if (selectedItems.MoveNext()) { - var currentId = selectedItems.Current.itemid; - string name; - GetCanonicalName(currentId, out name); - var nodeFolderNode = project.FindNodeByFullPath(name) as NodejsFolderNode; - - if (nodeFolderNode != null) { - if (nodeFolderNode.ContentType == FolderContentType.Browser) { - isClientCode = true; - } - } - } - - if (!isClientCode && _innerProject3 != null && IsJavaScriptFile(pszItemName)) { - Guid ourEditor = IsES6IntellisensePreview() ? Guid.Empty : typeof(NodejsEditorFactory).GUID; - Guid view = Guid.Empty; - return _innerProject3.AddItemWithSpecific( - itemidLoc, - dwAddItemOperation, - pszItemName, - cFilesToOpen, - rgpszFilesToOpen, - hwndDlgOwner, - dwAddItemOperation == VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE ? - (uint)__VSSPECIFICEDITORFLAGS.VSSPECIFICEDITOR_DoOpen : - 0, - ref ourEditor, - String.Empty, - ref view, - pResult - ); - } - return _innerProject.AddItem(itemidLoc, dwAddItemOperation, pszItemName, cFilesToOpen, rgpszFilesToOpen, hwndDlgOwner, pResult); - } - - public int GenerateUniqueItemName(uint itemidLoc, string pszExt, string pszSuggestedRoot, out string pbstrItemName) { - return _innerProject.GenerateUniqueItemName(itemidLoc, pszExt, pszSuggestedRoot, out pbstrItemName); - } - - public int GetItemContext(uint itemid, out VisualStudio.OLE.Interop.IServiceProvider ppSP) { - return _innerProject.GetItemContext(itemid, out ppSP); - } - - public int GetMkDocument(uint itemid, out string pbstrMkDocument) { - return _innerProject.GetMkDocument(itemid, out pbstrMkDocument); - } - - public int IsDocumentInProject(string pszMkDocument, out int pfFound, VSDOCUMENTPRIORITY[] pdwPriority, out uint pitemid) { - return _innerProject.IsDocumentInProject(pszMkDocument, out pfFound, pdwPriority, out pitemid); - } - - public int OpenItem(uint itemid, ref Guid rguidLogicalView, IntPtr punkDocDataExisting, out IVsWindowFrame ppWindowFrame) { - if (_innerProject3 != null && IsJavaScriptFile(GetItemName(_innerVsHierarchy, itemid))) { - // force .js files opened w/o an editor type to be opened w/ our editor factory. - Guid guid = IsES6IntellisensePreview() ? Guid.Empty : typeof(NodejsEditorFactory).GUID; - Guid view = Guid.Empty; - int hr = _innerProject3.OpenItemWithSpecific( - itemid, - 0, - ref guid, - null, - rguidLogicalView, - punkDocDataExisting, - out ppWindowFrame - ); - return hr; - } - - return _innerProject.OpenItem(itemid, rguidLogicalView, punkDocDataExisting, out ppWindowFrame); - } - - #endregion - - #region IVsProject2 Members - - public int RemoveItem(uint dwReserved, uint itemid, out int pfResult) { - if (_innerProject3 != null) { - return _innerProject3.RemoveItem(dwReserved, itemid, out pfResult); - } - pfResult = 0; - return VSConstants.E_NOTIMPL; - } - - public int ReopenItem(uint itemid, ref Guid rguidEditorType, string pszPhysicalView, ref Guid rguidLogicalView, IntPtr punkDocDataExisting, out IVsWindowFrame ppWindowFrame) { - if (_innerProject3 != null) { - if (IsJavaScriptFile(GetItemName(_innerVsHierarchy, itemid))) { - // force .js files opened w/o an editor type to be opened w/ our editor factory. - // If the item type of this file is not compile, we don't actually want to open with Nodejs and should instead use the default. - var itemType = GetExtensionObject(_innerVsHierarchy, itemid).Properties.Item("ItemType").Value; - Guid guid = (string)itemType == ProjectFileConstants.Compile && !IsES6IntellisensePreview() ? Guids.NodejsEditorFactory : Guid.Empty; - - return _innerProject3.ReopenItem( - itemid, - ref guid, - pszPhysicalView, - ref rguidLogicalView, - punkDocDataExisting, - out ppWindowFrame - ); - - } - return _innerProject3.ReopenItem(itemid, ref rguidEditorType, pszPhysicalView, ref rguidLogicalView, punkDocDataExisting, out ppWindowFrame); - } - ppWindowFrame = null; - return VSConstants.E_NOTIMPL; - } - - #endregion - - public void AddedAsRole(object azureProjectHierarchy, string roleType) { - var hier = azureProjectHierarchy as IVsHierarchy; - - if (hier == null) { - return; - } - - this._package.GetUIThread().Invoke(() => { - string caption; - object captionObj; - if (ErrorHandler.Failed(_innerVsHierarchy.GetProperty( - (uint)VSConstants.VSITEMID.Root, - (int)__VSHPROPID.VSHPROPID_Caption, - out captionObj - )) || string.IsNullOrEmpty(caption = captionObj as string)) { - return; - } - - UpdateServiceDefinition( - hier, - roleType, - caption, - new ServiceProvider(GetSite()) - ); - }); - } - - private static bool TryGetItemId(object obj, out uint id) { - const uint nil = (uint)VSConstants.VSITEMID.Nil; - id = obj as uint? ?? nil; - if (id == nil) { - var asInt = obj as int?; - if (asInt.HasValue) { - id = unchecked((uint)asInt.Value); - } - } - return id != nil; - } - - /// - /// Updates the ServiceDefinition.csdef file in - /// to include the default startup and - /// runtime tasks for Python projects. - /// - /// - /// The Cloud Service project to update. - /// - /// - /// The type of role being added, either "Web" or "Worker". - /// - /// - /// The name of the role. This typically matches the Caption property. - /// - /// - /// VS service provider. - /// - internal static void UpdateServiceDefinition( - IVsHierarchy project, - string roleType, - string projectName, - System.IServiceProvider site - ) { - Utilities.ArgumentNotNull("project", project); - - object obj; - ErrorHandler.ThrowOnFailure(project.GetProperty( - (uint)VSConstants.VSITEMID.Root, - (int)__VSHPROPID.VSHPROPID_FirstChild, - out obj - )); - - uint id; - while (TryGetItemId(obj, out id)) { - Guid itemType; - string mkDoc; - - if (ErrorHandler.Succeeded(project.GetGuidProperty(id, (int)__VSHPROPID.VSHPROPID_TypeGuid, out itemType)) && - itemType == VSConstants.GUID_ItemType_PhysicalFile && - ErrorHandler.Succeeded(project.GetProperty(id, (int)__VSHPROPID.VSHPROPID_Name, out obj)) && - "ServiceDefinition.csdef".Equals(obj as string, StringComparison.InvariantCultureIgnoreCase) && - ErrorHandler.Succeeded(project.GetCanonicalName(id, out mkDoc)) && - !string.IsNullOrEmpty(mkDoc) - ) { - // We have found the file - var rdt = site.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; - - IVsHierarchy docHier; - uint docId, docCookie; - IntPtr pDocData; - - bool updateFileOnDisk = true; - - if (ErrorHandler.Succeeded(rdt.FindAndLockDocument( - (uint)_VSRDTFLAGS.RDT_EditLock, - mkDoc, - out docHier, - out docId, - out pDocData, - out docCookie - ))) { - try { - if (pDocData != IntPtr.Zero) { - try { - // File is open, so edit it through the document - UpdateServiceDefinition( - Marshal.GetObjectForIUnknown(pDocData) as IVsTextLines, - roleType, - projectName - ); - - ErrorHandler.ThrowOnFailure(rdt.SaveDocuments( - (uint)__VSRDTSAVEOPTIONS.RDTSAVEOPT_ForceSave, - docHier, - docId, - docCookie - )); - - updateFileOnDisk = false; - } catch (ArgumentException) { - } catch (InvalidOperationException) { - } catch (COMException) { - } finally { - Marshal.Release(pDocData); - } - } - } finally { - ErrorHandler.ThrowOnFailure(rdt.UnlockDocument( - (uint)_VSRDTFLAGS.RDT_Unlock_SaveIfDirty | (uint)_VSRDTFLAGS.RDT_RequestUnlock, - docCookie - )); - } - } - - if (updateFileOnDisk) { - // File is not open, so edit it on disk - FileStream stream = null; - try { - UpdateServiceDefinition(mkDoc, roleType, projectName); - } finally { - if (stream != null) { - stream.Close(); - } - } - } - - break; - } - - if (ErrorHandler.Failed(project.GetProperty(id, (int)__VSHPROPID.VSHPROPID_NextSibling, out obj))) { - break; - } - } - } - - private class StringWriterWithEncoding : StringWriter { - private readonly Encoding _encoding; - - public StringWriterWithEncoding(Encoding encoding) { - _encoding = encoding; - } - - public override Encoding Encoding { - get { return _encoding; } - } - } - - private static void UpdateServiceDefinition(IVsTextLines lines, string roleType, string projectName) { - if (lines == null) { - throw new ArgumentException("lines"); - } - - int lastLine, lastIndex; - string text; - - ErrorHandler.ThrowOnFailure(lines.GetLastLineIndex(out lastLine, out lastIndex)); - ErrorHandler.ThrowOnFailure(lines.GetLineText(0, 0, lastLine, lastIndex, out text)); - - var doc = new XmlDocument(); - doc.LoadXml(text); - - UpdateServiceDefinition(doc, roleType, projectName); - - var encoding = Encoding.UTF8; - - var userData = lines as IVsUserData; - if (userData != null) { - var guid = VSConstants.VsTextBufferUserDataGuid.VsBufferEncodingVSTFF_guid; - object data; - int cp; - if (ErrorHandler.Succeeded(userData.GetData(ref guid, out data)) && - (cp = (data as int? ?? (int)(data as uint? ?? 0)) & (int)__VSTFF.VSTFF_CPMASK) != 0) { - try { - encoding = Encoding.GetEncoding(cp); - } catch (NotSupportedException) { - } catch (ArgumentException) { - } - } - } - - var sw = new StringWriterWithEncoding(encoding); - doc.Save(XmlWriter.Create( - sw, - new XmlWriterSettings { - Indent = true, - IndentChars = " ", - NewLineHandling = NewLineHandling.Entitize, - Encoding = encoding - } - )); - - var sb = sw.GetStringBuilder(); - var len = sb.Length; - var pStr = Marshal.StringToCoTaskMemUni(sb.ToString()); - - try { - ErrorHandler.ThrowOnFailure(lines.ReplaceLines(0, 0, lastLine, lastIndex, pStr, len, new TextSpan[1])); - } finally { - Marshal.FreeCoTaskMem(pStr); - } - } - - private static void UpdateServiceDefinition(string path, string roleType, string projectName) { - var doc = new XmlDocument(); - doc.Load(path); - - UpdateServiceDefinition(doc, roleType, projectName); - - doc.Save(XmlWriter.Create( - path, - new XmlWriterSettings { - Indent = true, - IndentChars = " ", - NewLineHandling = NewLineHandling.Entitize, - Encoding = Encoding.UTF8 - } - )); - } - - /// - /// Modifies the provided XML document to contain the service definition - /// nodes needed for the specified project. - /// - /// - /// is not one of "Web" or "Worker". - /// - /// - /// A required element is missing from the document. - /// - internal static void UpdateServiceDefinition(XmlDocument doc, string roleType, string projectName) { - bool isWeb = roleType == "Web"; - bool isWorker = roleType == "Worker"; - if (isWeb == isWorker) { - throw new ArgumentException("Unknown role type: " + (roleType ?? "(null)"), "roleType"); - } - - var nav = doc.CreateNavigator(); - - var ns = new XmlNamespaceManager(doc.NameTable); - ns.AddNamespace("sd", "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"); - - var role = nav.SelectSingleNode(string.Format( - "/sd:ServiceDefinition/sd:{0}Role[@name='{1}']", roleType, projectName - ), ns); - - if (role == null) { - throw new InvalidOperationException("Missing role entry"); - } - - var startup = role.SelectSingleNode("sd:Startup", ns); - if (startup != null) { - startup.DeleteSelf(); - } - - role.AppendChildElement(null, "Startup", null, null); - startup = role.SelectSingleNode("sd:Startup", ns); - if (startup == null) { - throw new InvalidOperationException("Missing Startup entry"); - } - - startup.ReplaceSelf(string.Format(@" - - - - - - - - - {1} -", roleType.ToLowerInvariant(), isWorker ? @"" : string.Empty)); - - if (isWorker) { - var runtime = role.SelectSingleNode("sd:Runtime", ns); - if (runtime != null) { - runtime.DeleteSelf(); - } - role.AppendChildElement(null, "Runtime", null, null); - - runtime = role.SelectSingleNode("sd:Runtime", ns); - if (startup == null) { - throw new InvalidOperationException("Missing Runtime entry"); - } - - runtime.ReplaceSelf(@" - - - - - - - - -"); - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; +using System.Text; +using System.Xml; +using Microsoft.NodejsTools.Project; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Azure; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Flavor; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.Project; + +namespace Microsoft.NodejsTools { + [Guid("78D985FC-2CA0-4D08-9B6B-35ACD5E5294A")] + class NodejsProject : FlavoredProjectBase, IOleCommandTarget, IVsProjectFlavorCfgProvider, IVsProject, IVsProject2, IAzureRoleProject { + internal IVsProject _innerProject; + internal IVsProject3 _innerProject3; + internal NodejsPackage _package; + private OleMenuCommandService _menuService; + private List _commands = new List(); + private IVsProjectFlavorCfgProvider _innerVsProjectFlavorCfgProvider; + + protected override void Close() { + if (_menuService != null) { + foreach (var command in _commands) { + _menuService.RemoveCommand(command); + } + _menuService.Dispose(); + } + _commands.Clear(); + base.Close(); + } + + protected override void InitializeForOuter(string fileName, string location, string name, uint flags, ref Guid guidProject, out bool cancel) { + CommandID menuCommandID = new CommandID(VSConstants.GUID_VSStandardCommandSet97, (int)VSConstants.VSStd97CmdID.Open); + OleMenuCommand menuItem = new OleMenuCommand(OpenFile, null, OpenFileBeforeQueryStatus, menuCommandID); + AddCommand(menuItem); + + menuCommandID = new CommandID(VSConstants.GUID_VSStandardCommandSet97, (int)VSConstants.VSStd97CmdID.ViewCode); + menuItem = new OleMenuCommand(OpenFile, null, OpenFileBeforeQueryStatus, menuCommandID); + AddCommand(menuItem); + + menuCommandID = new CommandID(VSConstants.VSStd2K, (int)VSConstants.VSStd2KCmdID.ECMD_VIEWMARKUP); + menuItem = new OleMenuCommand(OpenFile, null, OpenFileBeforeQueryStatus, menuCommandID); + AddCommand(menuItem); + + base.InitializeForOuter(fileName, location, name, flags, ref guidProject, out cancel); + + object extObject; + ErrorHandler.ThrowOnFailure( + _innerVsHierarchy.GetProperty( + VSConstants.VSITEMID_ROOT, + (int)__VSHPROPID.VSHPROPID_ExtObject, + out extObject + ) + ); + + var proj = extObject as EnvDTE.Project; + if (proj != null) { + try { + object webAppExtender = proj.get_Extender("WebApplication"); + if (webAppExtender != null && webAppExtender is WebAppExtenderFilter) { + ((dynamic)((WebAppExtenderFilter)webAppExtender).InnerObject).StartWebServerOnDebug = false; + } + } catch (COMException) { + // extender doesn't exist... + } + } + } + + private void AddCommand(OleMenuCommand menuItem) { + _menuService.AddCommand(menuItem); + _commands.Add(menuItem); + } + + private void OpenFile(object sender, EventArgs e) { + var oleMenu = sender as OleMenuCommand; + oleMenu.Supported = false; + + foreach (var vsItemSelection in GetSelectedItems()) { + if (IsJavaScriptFile(Name(vsItemSelection))) { + ErrorHandler.ThrowOnFailure(OpenWithNodejsEditor(vsItemSelection.itemid)); + } else { + ErrorHandler.ThrowOnFailure(OpenWithDefaultEditor(vsItemSelection.itemid)); + } + } + } + + private void OpenFileBeforeQueryStatus(object sender, EventArgs e) { + var oleMenu = sender as OleMenuCommand; + oleMenu.Supported = false; + + foreach (var vsItemSelection in GetSelectedItems()) { + object name; + ErrorHandler.ThrowOnFailure(vsItemSelection.pHier.GetProperty(vsItemSelection.itemid, (int)__VSHPROPID.VSHPROPID_Name, out name)); + + if (IsJavaScriptFile(Name(vsItemSelection))) { + oleMenu.Supported = true; + } + } + } + + internal static string Name(VSITEMSELECTION item) { + return GetItemName(item.pHier, item.itemid); + } + + internal static string GetItemName(IVsHierarchy hier, uint itemid) { + object name; + ErrorHandler.ThrowOnFailure(hier.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_Name, out name)); + return (string)name; + } + + private int OpenWithDefaultEditor(uint selectionItemId) { + Guid view = Guid.Empty; + IVsWindowFrame frame; + int hr = ((IVsProject)_innerVsHierarchy).OpenItem( + selectionItemId, + ref view, + IntPtr.Zero, + out frame + ); + if (ErrorHandler.Succeeded(hr)) { + hr = frame.Show(); + } + return hr; + } + + protected override void SetInnerProject(IntPtr innerIUnknown) { + var inner = Marshal.GetObjectForIUnknown(innerIUnknown); + + // The reason why we keep a reference to those is that doing a QI after being + // aggregated would do the AddRef on the outer object. + _innerProject = inner as IVsProject; + _innerProject3 = inner as IVsProject3; + _innerVsHierarchy = inner as IVsHierarchy; + _innerVsProjectFlavorCfgProvider = inner as IVsProjectFlavorCfgProvider; + + // Ensure we have a service provider as this is required for menu items to work + if (this.serviceProvider == null) + this.serviceProvider = (System.IServiceProvider)this._package; + + // Now let the base implementation set the inner object + base.SetInnerProject(innerIUnknown); + + // Add our commands (this must run after we called base.SetInnerProject) + _menuService = ((System.IServiceProvider)this).GetService(typeof(IMenuCommandService)) as OleMenuCommandService; + + } + + private bool TryHandleRightClick(IntPtr pvaIn, out int res) { + Guid itemType = GetSelectedItemType(); + + if (TryShowContextMenu(pvaIn, itemType, out res)) { + return true; + } + + return false; + } + + /// + /// Gets all of the currently selected items. + /// + /// + private IEnumerable GetSelectedItems() { + IVsMonitorSelection monitorSelection = _package.GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection; + + IntPtr hierarchyPtr = IntPtr.Zero; + IntPtr selectionContainer = IntPtr.Zero; + try { + uint selectionItemId; + IVsMultiItemSelect multiItemSelect = null; + ErrorHandler.ThrowOnFailure(monitorSelection.GetCurrentSelection(out hierarchyPtr, out selectionItemId, out multiItemSelect, out selectionContainer)); + + if (selectionItemId != VSConstants.VSITEMID_NIL && hierarchyPtr != IntPtr.Zero) { + IVsHierarchy hierarchy = Marshal.GetObjectForIUnknown(hierarchyPtr) as IVsHierarchy; + + if (selectionItemId != VSConstants.VSITEMID_SELECTION) { + // This is a single selection. Compare hirarchy with our hierarchy and get node from itemid + if (Utilities.IsSameComObject(this, hierarchy)) { + yield return new VSITEMSELECTION() { itemid = selectionItemId, pHier = hierarchy }; + } + } else if (multiItemSelect != null) { + // This is a multiple item selection. + // Get number of items selected and also determine if the items are located in more than one hierarchy + + uint numberOfSelectedItems; + int isSingleHierarchyInt; + ErrorHandler.ThrowOnFailure(multiItemSelect.GetSelectionInfo(out numberOfSelectedItems, out isSingleHierarchyInt)); + bool isSingleHierarchy = (isSingleHierarchyInt != 0); + + // Now loop all selected items and add to the list only those that are selected within this hierarchy + if (!isSingleHierarchy || (isSingleHierarchy && Utilities.IsSameComObject(this, hierarchy))) { + Debug.Assert(numberOfSelectedItems > 0, "Bad number of selected itemd"); + VSITEMSELECTION[] vsItemSelections = new VSITEMSELECTION[numberOfSelectedItems]; + uint flags = (isSingleHierarchy) ? (uint)__VSGSIFLAGS.GSI_fOmitHierPtrs : 0; + ErrorHandler.ThrowOnFailure(multiItemSelect.GetSelectedItems(flags, numberOfSelectedItems, vsItemSelections)); + + foreach (VSITEMSELECTION vsItemSelection in vsItemSelections) { + yield return new VSITEMSELECTION() { itemid = vsItemSelection.itemid, pHier = hierarchy }; + } + } + } + } + } finally { + if (hierarchyPtr != IntPtr.Zero) { + Marshal.Release(hierarchyPtr); + } + if (selectionContainer != IntPtr.Zero) { + Marshal.Release(selectionContainer); + } + } + } + + private Guid GetSelectedItemType() { + Guid itemType = Guid.Empty; + foreach (var vsItemSelection in GetSelectedItems()) { + Guid typeGuid = GetItemType(vsItemSelection); + + if (itemType == Guid.Empty) { + itemType = typeGuid; + } else if (itemType != typeGuid) { + // we have multiple item types + itemType = Guid.Empty; + break; + } + } + return itemType; + } + + private bool TryShowContextMenu(IntPtr pvaIn, Guid itemType, out int res) { + if (itemType == new Guid(Guids.NodejsProjectFactoryString)) { + // multiple Node prjoect nodes selected + res = ShowContextMenu(pvaIn, VsMenus.IDM_VS_CTXT_PROJNODE/*IDM_VS_CTXT_WEBPROJECT*/); + return true; + } else if (itemType == VSConstants.GUID_ItemType_PhysicalFile) { + // multiple files selected + res = ShowContextMenu(pvaIn, VsMenus.IDM_VS_CTXT_ITEMNODE); + return true; + } else if (itemType == VSConstants.GUID_ItemType_PhysicalFolder) { + res = ShowContextMenu(pvaIn, VsMenus.IDM_VS_CTXT_FOLDERNODE); + return true; + } + res = VSConstants.E_FAIL; + return false; + } + + private int ShowContextMenu(IntPtr pvaIn, int ctxMenu) { + object variant = Marshal.GetObjectForNativeVariant(pvaIn); + UInt32 pointsAsUint = (UInt32)variant; + short x = (short)(pointsAsUint & 0x0000ffff); + short y = (short)((pointsAsUint & 0xffff0000) / 0x10000); + + POINTS points = new POINTS(); + points.x = x; + points.y = y; + + return ShowContextMenu(ctxMenu, VsMenus.guidSHLMainMenu, points); + } + + /// + /// Shows the specified context menu at a specified location. + /// + /// The context menu ID. + /// The GUID of the menu group. + /// The location at which to show the menu. + internal int ShowContextMenu(int menuId, Guid menuGroup, POINTS points) { + IVsUIShell shell = _package.GetService(typeof(SVsUIShell)) as IVsUIShell; + + Debug.Assert(shell != null, "Could not get the ui shell from the project"); + if (shell == null) { + return VSConstants.E_FAIL; + } + POINTS[] pnts = new POINTS[1]; + pnts[0].x = points.x; + pnts[0].y = points.y; + return shell.ShowContextMenu(0, ref menuGroup, menuId, pnts, (Microsoft.VisualStudio.OLE.Interop.IOleCommandTarget)this); + } + + protected override int ExecCommand(uint itemid, ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + if (pguidCmdGroup == VsMenus.guidVsUIHierarchyWindowCmds) { + switch ((VSConstants.VsUIHierarchyWindowCmdIds)nCmdID) { + case VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_RightClick: + int res; + if (TryHandleRightClick(pvaIn, out res)) { + return res; + } + break; + case VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_DoubleClick: + case VSConstants.VsUIHierarchyWindowCmdIds.UIHWCMDID_EnterKey: + // open the document if it's an JavaScript file + if (IsJavaScriptFile(_innerVsHierarchy, itemid)) { + int hr = OpenWithNodejsEditor(itemid); + + if (ErrorHandler.Succeeded(hr)) { + return hr; + } + } + break; + + } + } + + var result = base.ExecCommand(itemid, ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + return result; + } + + int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + return ((IOleCommandTarget)_menuService).Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut); + } + + private bool IsJavaScriptFile(IVsHierarchy iVsHierarchy, uint itemid) { + object name; + ErrorHandler.ThrowOnFailure(iVsHierarchy.GetProperty(itemid, (int)__VSHPROPID.VSHPROPID_Name, out name)); + + return IsJavaScriptFile(name); + } + + private static bool IsJavaScriptFile(object name) { + string strName = name as string; + if (strName != null) { + var ext = Path.GetExtension(strName); + if (String.Equals(ext, ".js", StringComparison.OrdinalIgnoreCase)) { + return true; + } + } + return false; + } + + int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { + if (pguidCmdGroup == Guids.Eureka) { + for (int i = 0; i < prgCmds.Length; i++) { + switch (prgCmds[i].cmdID) { + case 0x102: // View in Web Page Inspector from Eureka web tools + prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_ENABLED); + return VSConstants.S_OK; + } + } + } else if (pguidCmdGroup == Guids.VenusCommandId) { + for (int i = 0; i < prgCmds.Length; i++) { + switch (prgCmds[i].cmdID) { + case 0x034: /* add app assembly folder */ + case 0x035: /* add app code folder */ + case 0x036: /* add global resources */ + case 0x037: /* add local resources */ + case 0x038: /* add web refs folder */ + case 0x039: /* add data folder */ + case 0x040: /* add browser folders */ + case 0x041: /* theme */ + case 0x054: /* package settings */ + case 0x055: /* context package settings */ + + prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_ENABLED); + return VSConstants.S_OK; + } + } + } else if (pguidCmdGroup == Guids.WebPackageCommandId) { + if (prgCmds[0].cmdID == 0x101 /* EnablePublishToWindowsAzureMenuItem*/) { + } + } else if (pguidCmdGroup == Guids.WebAppCmdId) { + for (int i = 0; i < prgCmds.Length; i++) { + switch (prgCmds[i].cmdID) { + case 0x06A: /* check accessibility */ + prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_DEFHIDEONCTXTMENU | OLECMDF.OLECMDF_ENABLED); + return VSConstants.S_OK; + } + } + } else if (pguidCmdGroup == VSConstants.VSStd2K) { + for (int i = 0; i < prgCmds.Length; i++) { + switch ((VSConstants.VSStd2KCmdID)prgCmds[i].cmdID) { + case VSConstants.VSStd2KCmdID.SETASSTARTPAGE: + case VSConstants.VSStd2KCmdID.CHECK_ACCESSIBILITY: + prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_DEFHIDEONCTXTMENU | OLECMDF.OLECMDF_ENABLED); + return VSConstants.S_OK; + } + } + } else if (pguidCmdGroup == VSConstants.GUID_VSStandardCommandSet97) { + for (int i = 0; i < prgCmds.Length; i++) { + switch ((VSConstants.VSStd97CmdID)prgCmds[i].cmdID) { + case VSConstants.VSStd97CmdID.PreviewInBrowser: + case VSConstants.VSStd97CmdID.BrowseWith: + prgCmds[i].cmdf = (uint)(OLECMDF.OLECMDF_INVISIBLE | OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_DEFHIDEONCTXTMENU | OLECMDF.OLECMDF_ENABLED); + return VSConstants.S_OK; + } + } + } + + return ((IOleCommandTarget)_menuService).QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText); + } + + + #region IVsProjectFlavorCfgProvider Members + + public int CreateProjectFlavorCfg(IVsCfg pBaseProjectCfg, out IVsProjectFlavorCfg ppFlavorCfg) { + // We're flavored with a Web Application project and our normal project... But we don't + // want the web application project to influence our config as that alters our debug + // launch story. We control that w/ the Django project which is actually just letting the + // base Node.js project handle it. So we keep the base Node.js project config here. + IVsProjectFlavorCfg webCfg; + ErrorHandler.ThrowOnFailure( + _innerVsProjectFlavorCfgProvider.CreateProjectFlavorCfg( + pBaseProjectCfg, + out webCfg + ) + ); + ppFlavorCfg = new NodejsProjectConfig(pBaseProjectCfg, webCfg); + + return VSConstants.S_OK; + } + + #endregion + + + protected override int GetProperty(uint itemId, int propId, out object property) { + switch ((__VSHPROPID)propId) { + case __VSHPROPID.VSHPROPID_IconIndex: + case __VSHPROPID.VSHPROPID_OpenFolderIconIndex: + // Venus wants to change the icon for special folders using the IconIndex. All of our + // folders respond to IconHandles so we just force folders down that code path rather + // than trying to hand out the correct IconIndex here + if (GetItemType(new VSITEMSELECTION() { itemid = itemId, pHier = this }) == VSConstants.GUID_ItemType_PhysicalFolder) { + property = null; + return VSConstants.DISP_E_MEMBERNOTFOUND; + } + break; + } + switch ((__VSHPROPID4)propId) { + + case __VSHPROPID4.VSHPROPID_TargetFrameworkMoniker: + // really only here for testing so WAP projects load correctly... + // But this also impacts the toolbox by filtering what available items there are. + property = ".NETFramework,Version=v4.5,Profile=Client"; + return VSConstants.S_OK; + } + switch ((__VSHPROPID2)propId) { + case __VSHPROPID2.VSHPROPID_CfgPropertyPagesCLSIDList: { + var res = base.GetProperty(itemId, propId, out property); + property = RemovePropertyPagesFromList((string)property, CfgSpecificPropertyPagesToRemove); + return res; + } + case __VSHPROPID2.VSHPROPID_PropertyPagesCLSIDList: { + var res = base.GetProperty(itemId, propId, out property); + property = RemovePropertyPagesFromList((string)property, PropertyPagesToRemove); + return res; + } + } +#if DEV14_OR_LATER + switch((__VSHPROPID8)propId) { + case __VSHPROPID8.VSHPROPID_SupportsIconMonikers: + property = true; + return VSConstants.S_OK; + } +#endif + + return base.GetProperty(itemId, propId, out property); + } + + internal static string[] CfgSpecificPropertyPagesToRemove = new[] { + "{A553AD0B-2F9E-4BCE-95B3-9A1F7074BC27}", // Package/Publish Web + "{9AB2347D-948D-4CD2-8DBE-F15F0EF78ED3}", // Package/Publish SQL + }; + + internal static string[] PropertyPagesToRemove = new[] { + "{8C0201FE-8ECA-403C-92A3-1BC55F031979}", // typeof(DeployPropertyPageComClass) + "{ED3B544C-26D8-4348-877B-A1F7BD505ED9}", // typeof(DatabaseDeployPropertyPageComClass) + "{909D16B3-C8E8-43D1-A2B8-26EA0D4B6B57}", // Microsoft.VisualStudio.Web.Application.WebPropertyPage + "{379354F2-BBB3-4BA9-AA71-FBE7B0E5EA94}" // Microsoft.VisualStudio.Web.Application.SilverlightLinksPage + }; + + internal string RemovePropertyPagesFromList(string propertyPagesList, string[] pagesToRemove) { + if (pagesToRemove != null) { + propertyPagesList = propertyPagesList.ToUpper(CultureInfo.InvariantCulture); + foreach (string s in pagesToRemove) { + int index = propertyPagesList.IndexOf(s, StringComparison.Ordinal); + if (index != -1) { + // Guids are separated by ';' so if we remove the last one also remove the last ';' + int index2 = index + s.Length + 1; + if (index2 >= propertyPagesList.Length) + propertyPagesList = propertyPagesList.Substring(0, index).TrimEnd(';'); + else + propertyPagesList = propertyPagesList.Substring(0, index) + propertyPagesList.Substring(index2); + } + } + } + return propertyPagesList; + } + + internal static Guid GetItemType(VSITEMSELECTION vsItemSelection) { + Guid typeGuid; + try { + ErrorHandler.ThrowOnFailure( + vsItemSelection.pHier.GetGuidProperty( + vsItemSelection.itemid, + (int)__VSHPROPID.VSHPROPID_TypeGuid, + out typeGuid + ) + ); + } catch (System.Runtime.InteropServices.COMException) { + return Guid.Empty; + } + return typeGuid; + } + + private static EnvDTE.ProjectItem GetExtensionObject(IVsHierarchy hierarchy, uint itemId) { + object project; + + ErrorHandler.ThrowOnFailure( + hierarchy.GetProperty( + itemId, + (int)__VSHPROPID.VSHPROPID_ExtObject, + out project + ) + ); + + return (project as EnvDTE.ProjectItem); + } + + private int OpenWithNodejsEditor(uint selectionItemId) { + // If the item type of this file is not compile, we don't actually want to open with Nodejs and should instead use the default. + Guid ourEditor; + var properties = GetExtensionObject(_innerVsHierarchy, selectionItemId).Properties; + try { + var itemType = properties.Item("ItemType").Value; + ourEditor = (string)itemType == ProjectFileConstants.Compile && !IsES6IntellisensePreview() ? Guids.NodejsEditorFactory : Guid.Empty; + } catch (ArgumentException) { + // no item type, file is excluded from project. + ourEditor = Guids.NodejsEditorFactory; + } + + Guid view = Guid.Empty; + IVsWindowFrame frame; + + // DOCDATAEXISTING_UNKNOWN http://msdn.microsoft.com/en-us/library/vstudio/bb139396(v=vs.110).aspx + // Force OpenStandardEditor to lookup if the document is currently open or not, and if it is. If it's + // open in a different editor the user will be prompted to close it. + var docDataExistingUnknown = new IntPtr(-1); + int hr = ((IVsProject3)_innerVsHierarchy).OpenItemWithSpecific( + selectionItemId, + 0, + ref ourEditor, + null, + ref view, + docDataExistingUnknown, + out frame + ); + if (frame != null && ErrorHandler.Succeeded(hr)) { + hr = frame.Show(); + } + return hr; + } + + private bool IsES6IntellisensePreview() { + // If the analysis level is set to preview then use the TypeScript language service for node.js + return NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevel == Options.AnalysisLevel.Preview; + } + + #region IVsProject Members + + public int AddItem(uint itemidLoc, VSADDITEMOPERATION dwAddItemOperation, string pszItemName, uint cFilesToOpen, string[] rgpszFilesToOpen, IntPtr hwndDlgOwner, VSADDRESULT[] pResult) { + // Check if we are adding an item to a folder that consists of browser-side code. + // In this case, we will want to open the file with the default editor. + var isClientCode = false; + var project = _innerVsHierarchy.GetProject().GetNodejsProject(); + + var selectedItems = this.GetSelectedItems().GetEnumerator(); + if (selectedItems.MoveNext()) { + var currentId = selectedItems.Current.itemid; + string name; + GetCanonicalName(currentId, out name); + var nodeFolderNode = project.FindNodeByFullPath(name) as NodejsFolderNode; + + if (nodeFolderNode != null) { + if (nodeFolderNode.ContentType == FolderContentType.Browser) { + isClientCode = true; + } + } + } + + if (!isClientCode && _innerProject3 != null && IsJavaScriptFile(pszItemName)) { + Guid ourEditor = IsES6IntellisensePreview() ? Guid.Empty : typeof(NodejsEditorFactory).GUID; + Guid view = Guid.Empty; + return _innerProject3.AddItemWithSpecific( + itemidLoc, + dwAddItemOperation, + pszItemName, + cFilesToOpen, + rgpszFilesToOpen, + hwndDlgOwner, + dwAddItemOperation == VSADDITEMOPERATION.VSADDITEMOP_CLONEFILE ? + (uint)__VSSPECIFICEDITORFLAGS.VSSPECIFICEDITOR_DoOpen : + 0, + ref ourEditor, + String.Empty, + ref view, + pResult + ); + } + return _innerProject.AddItem(itemidLoc, dwAddItemOperation, pszItemName, cFilesToOpen, rgpszFilesToOpen, hwndDlgOwner, pResult); + } + + public int GenerateUniqueItemName(uint itemidLoc, string pszExt, string pszSuggestedRoot, out string pbstrItemName) { + return _innerProject.GenerateUniqueItemName(itemidLoc, pszExt, pszSuggestedRoot, out pbstrItemName); + } + + public int GetItemContext(uint itemid, out VisualStudio.OLE.Interop.IServiceProvider ppSP) { + return _innerProject.GetItemContext(itemid, out ppSP); + } + + public int GetMkDocument(uint itemid, out string pbstrMkDocument) { + return _innerProject.GetMkDocument(itemid, out pbstrMkDocument); + } + + public int IsDocumentInProject(string pszMkDocument, out int pfFound, VSDOCUMENTPRIORITY[] pdwPriority, out uint pitemid) { + return _innerProject.IsDocumentInProject(pszMkDocument, out pfFound, pdwPriority, out pitemid); + } + + public int OpenItem(uint itemid, ref Guid rguidLogicalView, IntPtr punkDocDataExisting, out IVsWindowFrame ppWindowFrame) { + if (_innerProject3 != null && IsJavaScriptFile(GetItemName(_innerVsHierarchy, itemid))) { + // force .js files opened w/o an editor type to be opened w/ our editor factory. + Guid guid = IsES6IntellisensePreview() ? Guid.Empty : typeof(NodejsEditorFactory).GUID; + Guid view = Guid.Empty; + int hr = _innerProject3.OpenItemWithSpecific( + itemid, + 0, + ref guid, + null, + rguidLogicalView, + punkDocDataExisting, + out ppWindowFrame + ); + return hr; + } + + return _innerProject.OpenItem(itemid, rguidLogicalView, punkDocDataExisting, out ppWindowFrame); + } + + #endregion + + #region IVsProject2 Members + + public int RemoveItem(uint dwReserved, uint itemid, out int pfResult) { + if (_innerProject3 != null) { + return _innerProject3.RemoveItem(dwReserved, itemid, out pfResult); + } + pfResult = 0; + return VSConstants.E_NOTIMPL; + } + + public int ReopenItem(uint itemid, ref Guid rguidEditorType, string pszPhysicalView, ref Guid rguidLogicalView, IntPtr punkDocDataExisting, out IVsWindowFrame ppWindowFrame) { + if (_innerProject3 != null) { + if (IsJavaScriptFile(GetItemName(_innerVsHierarchy, itemid))) { + // force .js files opened w/o an editor type to be opened w/ our editor factory. + // If the item type of this file is not compile, we don't actually want to open with Nodejs and should instead use the default. + var itemType = GetExtensionObject(_innerVsHierarchy, itemid).Properties.Item("ItemType").Value; + Guid guid = (string)itemType == ProjectFileConstants.Compile && !IsES6IntellisensePreview() ? Guids.NodejsEditorFactory : Guid.Empty; + + return _innerProject3.ReopenItem( + itemid, + ref guid, + pszPhysicalView, + ref rguidLogicalView, + punkDocDataExisting, + out ppWindowFrame + ); + + } + return _innerProject3.ReopenItem(itemid, ref rguidEditorType, pszPhysicalView, ref rguidLogicalView, punkDocDataExisting, out ppWindowFrame); + } + ppWindowFrame = null; + return VSConstants.E_NOTIMPL; + } + + #endregion + + public void AddedAsRole(object azureProjectHierarchy, string roleType) { + var hier = azureProjectHierarchy as IVsHierarchy; + + if (hier == null) { + return; + } + + this._package.GetUIThread().Invoke(() => { + string caption; + object captionObj; + if (ErrorHandler.Failed(_innerVsHierarchy.GetProperty( + (uint)VSConstants.VSITEMID.Root, + (int)__VSHPROPID.VSHPROPID_Caption, + out captionObj + )) || string.IsNullOrEmpty(caption = captionObj as string)) { + return; + } + + UpdateServiceDefinition( + hier, + roleType, + caption, + new ServiceProvider(GetSite()) + ); + }); + } + + private static bool TryGetItemId(object obj, out uint id) { + const uint nil = (uint)VSConstants.VSITEMID.Nil; + id = obj as uint? ?? nil; + if (id == nil) { + var asInt = obj as int?; + if (asInt.HasValue) { + id = unchecked((uint)asInt.Value); + } + } + return id != nil; + } + + /// + /// Updates the ServiceDefinition.csdef file in + /// to include the default startup and + /// runtime tasks for Python projects. + /// + /// + /// The Cloud Service project to update. + /// + /// + /// The type of role being added, either "Web" or "Worker". + /// + /// + /// The name of the role. This typically matches the Caption property. + /// + /// + /// VS service provider. + /// + internal static void UpdateServiceDefinition( + IVsHierarchy project, + string roleType, + string projectName, + System.IServiceProvider site + ) { + Utilities.ArgumentNotNull("project", project); + + object obj; + ErrorHandler.ThrowOnFailure(project.GetProperty( + (uint)VSConstants.VSITEMID.Root, + (int)__VSHPROPID.VSHPROPID_FirstChild, + out obj + )); + + uint id; + while (TryGetItemId(obj, out id)) { + Guid itemType; + string mkDoc; + + if (ErrorHandler.Succeeded(project.GetGuidProperty(id, (int)__VSHPROPID.VSHPROPID_TypeGuid, out itemType)) && + itemType == VSConstants.GUID_ItemType_PhysicalFile && + ErrorHandler.Succeeded(project.GetProperty(id, (int)__VSHPROPID.VSHPROPID_Name, out obj)) && + "ServiceDefinition.csdef".Equals(obj as string, StringComparison.InvariantCultureIgnoreCase) && + ErrorHandler.Succeeded(project.GetCanonicalName(id, out mkDoc)) && + !string.IsNullOrEmpty(mkDoc) + ) { + // We have found the file + var rdt = site.GetService(typeof(SVsRunningDocumentTable)) as IVsRunningDocumentTable; + + IVsHierarchy docHier; + uint docId, docCookie; + IntPtr pDocData; + + bool updateFileOnDisk = true; + + if (ErrorHandler.Succeeded(rdt.FindAndLockDocument( + (uint)_VSRDTFLAGS.RDT_EditLock, + mkDoc, + out docHier, + out docId, + out pDocData, + out docCookie + ))) { + try { + if (pDocData != IntPtr.Zero) { + try { + // File is open, so edit it through the document + UpdateServiceDefinition( + Marshal.GetObjectForIUnknown(pDocData) as IVsTextLines, + roleType, + projectName + ); + + ErrorHandler.ThrowOnFailure(rdt.SaveDocuments( + (uint)__VSRDTSAVEOPTIONS.RDTSAVEOPT_ForceSave, + docHier, + docId, + docCookie + )); + + updateFileOnDisk = false; + } catch (ArgumentException) { + } catch (InvalidOperationException) { + } catch (COMException) { + } finally { + Marshal.Release(pDocData); + } + } + } finally { + ErrorHandler.ThrowOnFailure(rdt.UnlockDocument( + (uint)_VSRDTFLAGS.RDT_Unlock_SaveIfDirty | (uint)_VSRDTFLAGS.RDT_RequestUnlock, + docCookie + )); + } + } + + if (updateFileOnDisk) { + // File is not open, so edit it on disk + FileStream stream = null; + try { + UpdateServiceDefinition(mkDoc, roleType, projectName); + } finally { + if (stream != null) { + stream.Close(); + } + } + } + + break; + } + + if (ErrorHandler.Failed(project.GetProperty(id, (int)__VSHPROPID.VSHPROPID_NextSibling, out obj))) { + break; + } + } + } + + private class StringWriterWithEncoding : StringWriter { + private readonly Encoding _encoding; + + public StringWriterWithEncoding(Encoding encoding) { + _encoding = encoding; + } + + public override Encoding Encoding { + get { return _encoding; } + } + } + + private static void UpdateServiceDefinition(IVsTextLines lines, string roleType, string projectName) { + if (lines == null) { + throw new ArgumentException("lines"); + } + + int lastLine, lastIndex; + string text; + + ErrorHandler.ThrowOnFailure(lines.GetLastLineIndex(out lastLine, out lastIndex)); + ErrorHandler.ThrowOnFailure(lines.GetLineText(0, 0, lastLine, lastIndex, out text)); + + var doc = new XmlDocument(); + doc.LoadXml(text); + + UpdateServiceDefinition(doc, roleType, projectName); + + var encoding = Encoding.UTF8; + + var userData = lines as IVsUserData; + if (userData != null) { + var guid = VSConstants.VsTextBufferUserDataGuid.VsBufferEncodingVSTFF_guid; + object data; + int cp; + if (ErrorHandler.Succeeded(userData.GetData(ref guid, out data)) && + (cp = (data as int? ?? (int)(data as uint? ?? 0)) & (int)__VSTFF.VSTFF_CPMASK) != 0) { + try { + encoding = Encoding.GetEncoding(cp); + } catch (NotSupportedException) { + } catch (ArgumentException) { + } + } + } + + var sw = new StringWriterWithEncoding(encoding); + doc.Save(XmlWriter.Create( + sw, + new XmlWriterSettings { + Indent = true, + IndentChars = " ", + NewLineHandling = NewLineHandling.Entitize, + Encoding = encoding + } + )); + + var sb = sw.GetStringBuilder(); + var len = sb.Length; + var pStr = Marshal.StringToCoTaskMemUni(sb.ToString()); + + try { + ErrorHandler.ThrowOnFailure(lines.ReplaceLines(0, 0, lastLine, lastIndex, pStr, len, new TextSpan[1])); + } finally { + Marshal.FreeCoTaskMem(pStr); + } + } + + private static void UpdateServiceDefinition(string path, string roleType, string projectName) { + var doc = new XmlDocument(); + doc.Load(path); + + UpdateServiceDefinition(doc, roleType, projectName); + + doc.Save(XmlWriter.Create( + path, + new XmlWriterSettings { + Indent = true, + IndentChars = " ", + NewLineHandling = NewLineHandling.Entitize, + Encoding = Encoding.UTF8 + } + )); + } + + /// + /// Modifies the provided XML document to contain the service definition + /// nodes needed for the specified project. + /// + /// + /// is not one of "Web" or "Worker". + /// + /// + /// A required element is missing from the document. + /// + internal static void UpdateServiceDefinition(XmlDocument doc, string roleType, string projectName) { + bool isWeb = roleType == "Web"; + bool isWorker = roleType == "Worker"; + if (isWeb == isWorker) { + throw new ArgumentException("Unknown role type: " + (roleType ?? "(null)"), "roleType"); + } + + var nav = doc.CreateNavigator(); + + var ns = new XmlNamespaceManager(doc.NameTable); + ns.AddNamespace("sd", "http://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition"); + + var role = nav.SelectSingleNode(string.Format( + "/sd:ServiceDefinition/sd:{0}Role[@name='{1}']", roleType, projectName + ), ns); + + if (role == null) { + throw new InvalidOperationException("Missing role entry"); + } + + var startup = role.SelectSingleNode("sd:Startup", ns); + if (startup != null) { + startup.DeleteSelf(); + } + + role.AppendChildElement(null, "Startup", null, null); + startup = role.SelectSingleNode("sd:Startup", ns); + if (startup == null) { + throw new InvalidOperationException("Missing Startup entry"); + } + + startup.ReplaceSelf(string.Format(@" + + + + + + + + + {1} +", roleType.ToLowerInvariant(), isWorker ? @"" : string.Empty)); + + if (isWorker) { + var runtime = role.SelectSingleNode("sd:Runtime", ns); + if (runtime != null) { + runtime.DeleteSelf(); + } + role.AppendChildElement(null, "Runtime", null, null); + + runtime = role.SelectSingleNode("sd:Runtime", ns); + if (startup == null) { + throw new InvalidOperationException("Missing Runtime entry"); + } + + runtime.ReplaceSelf(@" + + + + + + + + +"); + } + } + } +} diff --git a/Nodejs/Product/Nodejs/NpmUI/ErrorHelper.cs b/Nodejs/Product/Nodejs/NpmUI/ErrorHelper.cs index f715e82b1..0afef14bd 100644 --- a/Nodejs/Product/Nodejs/NpmUI/ErrorHelper.cs +++ b/Nodejs/Product/Nodejs/NpmUI/ErrorHelper.cs @@ -1,101 +1,101 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Text; -using System.Windows; -using Microsoft.NodejsTools.Npm; - -namespace Microsoft.NodejsTools.NpmUI -{ - internal static class ErrorHelper { - - private const string CaptionNpmNotInstalled = "npm Not Installed"; - - private static Exception _lastNpmNotFoundException; - - public static string GetExceptionDetailsText(Exception e) { - var buff = new StringBuilder(); - var current = e; - do { - if (buff.Length > 0) { - buff.Append("Caused by:\r\n"); - } - buff.Append(current.Message); - buff.Append("\r\n"); -#if DEBUG - buff.Append(current.GetType()); - buff.Append("\r\n"); - buff.Append(current.StackTrace); -#endif - current = current.InnerException; - } while (null != current); - return buff.ToString(); - } - - private static Exception GetNpmNotFoundException(Exception source) { - do { - if (source is NpmNotFoundException) { - return source; - } - source = source.InnerException; - } while (null != source); - return null; - } - - public static void ReportNpmNotInstalled( - Window owner, - Exception ex) { - var nnfe = GetNpmNotFoundException(ex); - if (null == nnfe) { - nnfe = ex; - } else { - // Don't want to keep bombarding user with same message - there's a real danger this popup - // could appear quite a lot if changes are made to the filesystem. - bool report = (null == _lastNpmNotFoundException || ex.Message != _lastNpmNotFoundException.Message); - - _lastNpmNotFoundException = nnfe; - - if (!report) { - return; - } - } - - var message = - string.Format(@"Could not find npm.cmd. Ensure you have a recent version of node.js installed and have specified the location of node.exe in the project properties, or that it is available on your system PATH. - -The following error occurred trying to execute npm.cmd: - -{0}", nnfe.Message); - - if (null == owner) { - MessageBox.Show( - message, - CaptionNpmNotInstalled, - MessageBoxButton.OK, - MessageBoxImage.Error); - } else { - MessageBox.Show( - owner, - message, - CaptionNpmNotInstalled, - MessageBoxButton.OK, - MessageBoxImage.Error); - } - - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Text; +using System.Windows; +using Microsoft.NodejsTools.Npm; + +namespace Microsoft.NodejsTools.NpmUI +{ + internal static class ErrorHelper { + + private const string CaptionNpmNotInstalled = "npm Not Installed"; + + private static Exception _lastNpmNotFoundException; + + public static string GetExceptionDetailsText(Exception e) { + var buff = new StringBuilder(); + var current = e; + do { + if (buff.Length > 0) { + buff.Append("Caused by:\r\n"); + } + buff.Append(current.Message); + buff.Append("\r\n"); +#if DEBUG + buff.Append(current.GetType()); + buff.Append("\r\n"); + buff.Append(current.StackTrace); +#endif + current = current.InnerException; + } while (null != current); + return buff.ToString(); + } + + private static Exception GetNpmNotFoundException(Exception source) { + do { + if (source is NpmNotFoundException) { + return source; + } + source = source.InnerException; + } while (null != source); + return null; + } + + public static void ReportNpmNotInstalled( + Window owner, + Exception ex) { + var nnfe = GetNpmNotFoundException(ex); + if (null == nnfe) { + nnfe = ex; + } else { + // Don't want to keep bombarding user with same message - there's a real danger this popup + // could appear quite a lot if changes are made to the filesystem. + bool report = (null == _lastNpmNotFoundException || ex.Message != _lastNpmNotFoundException.Message); + + _lastNpmNotFoundException = nnfe; + + if (!report) { + return; + } + } + + var message = + string.Format(@"Could not find npm.cmd. Ensure you have a recent version of node.js installed and have specified the location of node.exe in the project properties, or that it is available on your system PATH. + +The following error occurred trying to execute npm.cmd: + +{0}", nnfe.Message); + + if (null == owner) { + MessageBox.Show( + message, + CaptionNpmNotInstalled, + MessageBoxButton.OK, + MessageBoxImage.Error); + } else { + MessageBox.Show( + owner, + message, + CaptionNpmNotInstalled, + MessageBoxButton.OK, + MessageBoxImage.Error); + } + + } + } +} diff --git a/Nodejs/Product/Nodejs/NpmUI/LastRefreshedMessageProvider.cs b/Nodejs/Product/Nodejs/NpmUI/LastRefreshedMessageProvider.cs index 66cf4d574..79f4eb285 100644 --- a/Nodejs/Product/Nodejs/NpmUI/LastRefreshedMessageProvider.cs +++ b/Nodejs/Product/Nodejs/NpmUI/LastRefreshedMessageProvider.cs @@ -1,72 +1,72 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using Microsoft.NodejsTools.Project; - -namespace Microsoft.NodejsTools.NpmUI { - internal class LastRefreshedMessageProvider { - public static readonly LastRefreshedMessageProvider RefreshFailed = new LastRefreshedMessageProvider { - Days = int.MaxValue, - Description = SR.GetString(SR.PackageCatalogRefreshFailed) - }; - - public static readonly LastRefreshedMessageProvider RefreshInProgress = new LastRefreshedMessageProvider { - Days = 0, - Description = SR.GetString(SR.PackageCatalogRefreshing) - }; - - public static readonly LastRefreshedMessageProvider NpmNotFound = new LastRefreshedMessageProvider { - Days = int.MaxValue, - Description = "npm not installed" - }; - - private LastRefreshedMessageProvider() { } - - public LastRefreshedMessageProvider(DateTime lastRefreshTime) { - if (lastRefreshTime == DateTime.MinValue) { - Days = int.MaxValue; - Description = SR.GetString(SR.PackageCatalogRefreshFailed); - } else { - Days = (int)(DateTime.Now.Date - lastRefreshTime.Date).TotalDays; - if (Days == 0) { - Description = SR.GetString(SR.PackageCatalogRefresh0Days, lastRefreshTime); - } else if (Days == 1) { - Description = SR.GetString(SR.PackageCatalogRefresh1Day, lastRefreshTime); - } else if (Days <= 7) { - Description = SR.GetString(SR.PackageCatalogRefresh2To7Days, Days); - } else if (Days <= 14) { - Description = SR.GetString(SR.PackageCatalogRefresh1Week); - } else if (Days <= 21) { - Description = SR.GetString(SR.PackageCatalogRefresh2Weeks); - } else if (Days <= 31) { - Description = SR.GetString(SR.PackageCatalogRefresh3Weeks); - } else if (Days <= 92) { - Description = SR.GetString(SR.PackageCatalogRefresh1Month); - } else { - Description = SR.GetString(SR.PackageCatalogRefresh3Months); - } - } - } - - public int Days { get; private set; } - - public string Description { get; private set; } - - public bool IsOld { get { return Days > 7; } } - public bool IsAncient { get { return Days > 14; } } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using Microsoft.NodejsTools.Project; + +namespace Microsoft.NodejsTools.NpmUI { + internal class LastRefreshedMessageProvider { + public static readonly LastRefreshedMessageProvider RefreshFailed = new LastRefreshedMessageProvider { + Days = int.MaxValue, + Description = SR.GetString(SR.PackageCatalogRefreshFailed) + }; + + public static readonly LastRefreshedMessageProvider RefreshInProgress = new LastRefreshedMessageProvider { + Days = 0, + Description = SR.GetString(SR.PackageCatalogRefreshing) + }; + + public static readonly LastRefreshedMessageProvider NpmNotFound = new LastRefreshedMessageProvider { + Days = int.MaxValue, + Description = "npm not installed" + }; + + private LastRefreshedMessageProvider() { } + + public LastRefreshedMessageProvider(DateTime lastRefreshTime) { + if (lastRefreshTime == DateTime.MinValue) { + Days = int.MaxValue; + Description = SR.GetString(SR.PackageCatalogRefreshFailed); + } else { + Days = (int)(DateTime.Now.Date - lastRefreshTime.Date).TotalDays; + if (Days == 0) { + Description = SR.GetString(SR.PackageCatalogRefresh0Days, lastRefreshTime); + } else if (Days == 1) { + Description = SR.GetString(SR.PackageCatalogRefresh1Day, lastRefreshTime); + } else if (Days <= 7) { + Description = SR.GetString(SR.PackageCatalogRefresh2To7Days, Days); + } else if (Days <= 14) { + Description = SR.GetString(SR.PackageCatalogRefresh1Week); + } else if (Days <= 21) { + Description = SR.GetString(SR.PackageCatalogRefresh2Weeks); + } else if (Days <= 31) { + Description = SR.GetString(SR.PackageCatalogRefresh3Weeks); + } else if (Days <= 92) { + Description = SR.GetString(SR.PackageCatalogRefresh1Month); + } else { + Description = SR.GetString(SR.PackageCatalogRefresh3Months); + } + } + } + + public int Days { get; private set; } + + public string Description { get; private set; } + + public bool IsOld { get { return Days > 7; } } + public bool IsAncient { get { return Days > 14; } } + } +} diff --git a/Nodejs/Product/Nodejs/NpmUI/NpmPackageInstallViewModel.cs b/Nodejs/Product/Nodejs/NpmUI/NpmPackageInstallViewModel.cs index 365cd7440..6f80f01f3 100644 --- a/Nodejs/Product/Nodejs/NpmUI/NpmPackageInstallViewModel.cs +++ b/Nodejs/Product/Nodejs/NpmUI/NpmPackageInstallViewModel.cs @@ -1,513 +1,513 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Windows; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Threading; -using Microsoft.NodejsTools.Npm; -using Microsoft.NodejsTools.Project; - -namespace Microsoft.NodejsTools.NpmUI { - internal class NpmPackageInstallViewModel : INotifyPropertyChanged { - internal enum Indices { - IndexStandard = 0, - IndexDev = 1, - IndexOptional = 2, - IndexGlobal = 3 - } - - internal enum FilterState { - NoFilterText, - Filtering, - ResultsAvailable, - NoResults - } - - public static readonly ICommand InstallCommand = new RoutedCommand(); - public static readonly ICommand OpenHomepageCommand = new RoutedCommand(); - public static readonly ICommand RefreshCatalogCommand = new RoutedCommand(); - - private INpmController _npmController; - - private bool _isFiltering = false; - private bool _isLoadingCatalog; - private IPackageCatalog _allPackages; - private readonly object _filteredPackagesLock = new object(); - private IList _filteredPackages = new List(); - private LastRefreshedMessageProvider _lastRefreshedMessage; - private PackageCatalogEntryViewModel _selectedPackage; - private bool _npmNotFound; - private bool _isCatalogEmpty; - private Visibility _catalogControlVisibility = Visibility.Collapsed; - private string _catalogLoadingMessage = string.Empty; - private string _catalogLoadingProgressMessage = string.Empty; - private Visibility _loadingCatalogControlVisibility = Visibility.Collapsed; - private int _selectedDependencyTypeIndex; - - private string _currentFilter = string.Empty; - private string _filterText = string.Empty; - private readonly Timer _filterTimer; - private string _arguments = string.Empty; - private bool _saveToPackageJson = true; - private object _selectedVersion; - - private readonly Dispatcher _dispatcher; - - private readonly NpmOutputViewModel _executeViewModel; - - public NpmPackageInstallViewModel( - NpmOutputViewModel executeViewModel, - Dispatcher dispatcher - ) { - _dispatcher = dispatcher; - - _executeViewModel = executeViewModel; - _filterTimer = new Timer(FilterTimer_Elapsed, null, Timeout.Infinite, Timeout.Infinite); - } - - public event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { - PropertyChangedEventHandler handler = PropertyChanged; - if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); - } - - public INpmController NpmController { - get { return _npmController; } - set { - if (null != _npmController) { - _npmController.FinishedRefresh -= NpmController_FinishedRefresh; - } - _npmController = value; - OnPropertyChanged(); - if (null != _npmController) { - LoadCatalog(); - _npmController.FinishedRefresh += NpmController_FinishedRefresh; - } - } - } - - void NpmController_FinishedRefresh(object sender, EventArgs e) { - StartFilter(); - } - - public NpmOutputViewModel ExecuteViewModel { - get { return _executeViewModel; } - } - - #region Catalog control and refresh - public bool IsLoadingCatalog { - get { return _isLoadingCatalog; } - private set { - _isLoadingCatalog = value; - OnPropertyChanged(); - OnPropertyChanged("CanRefreshCatalog"); - } - } - - public bool CanRefreshCatalog { - get { return !IsLoadingCatalog; } - } - - public bool NpmNotFound { - get { return _npmNotFound; } - private set { - _npmNotFound = value; - OnPropertyChanged(); - } - } - - public bool IsCatalogEmpty { - get { return _isCatalogEmpty; } - private set { - _isCatalogEmpty = value; - OnPropertyChanged(); - } - } - - public string LoadingCatalogMessage { - get { return _catalogLoadingMessage; } - private set { - _catalogLoadingMessage = value; - OnPropertyChanged(); - } - } - - public string LoadingCatalogProgressMessage { - get { return _catalogLoadingProgressMessage; } - private set { - _catalogLoadingProgressMessage = value; - OnPropertyChanged(); - } - } - - public Visibility LoadingCatalogControlVisibility { - get { return _loadingCatalogControlVisibility; } - set { - _loadingCatalogControlVisibility = value; - OnPropertyChanged(); - OnPropertyChanged("FilterControlsVisibility"); - } - } - - private async void LoadCatalog(bool forceRefresh) { - IsLoadingCatalog = true; - - CatalogControlVisibility = Visibility.Collapsed; - LoadingCatalogControlVisibility = Visibility.Visible; - LoadingCatalogMessage = SR.GetString(SR.CatalogLoadingDefault); - - LastRefreshedMessage = LastRefreshedMessageProvider.RefreshInProgress; - - var controller = _npmController; - controller.ErrorLogged += _executeViewModel.commander_ErrorLogged; - controller.ExceptionLogged += _executeViewModel.commander_ExceptionLogged; - controller.OutputLogged += _executeViewModel.commander_OutputLogged; - _executeViewModel.SetCancellableSafe(false); - try { - _allPackages = await controller.GetRepositoryCatalogAsync( - forceRefresh, - new Progress(msg => LoadingCatalogProgressMessage = msg) - ); - IsCatalogEmpty = false; - } catch (NpmNotFoundException) { - LastRefreshedMessage = LastRefreshedMessageProvider.NpmNotFound; - } catch (NpmCatalogEmptyException) { - IsCatalogEmpty = true; - LastRefreshedMessage = new LastRefreshedMessageProvider(_allPackages.LastRefreshed); - } catch (Exception ex) { - if (IsCriticalException(ex)) { - throw; - } - - LastRefreshedMessage = LastRefreshedMessageProvider.RefreshFailed; - IsCatalogEmpty = true; - } finally { - IsLoadingCatalog = false; - controller.ErrorLogged -= _executeViewModel.commander_ErrorLogged; - controller.ExceptionLogged -= _executeViewModel.commander_ExceptionLogged; - controller.OutputLogged -= _executeViewModel.commander_OutputLogged; - _executeViewModel.SetCancellableSafe(true); - - // The catalog refresh operation spawns many long-lived Gen 2 objects, - // so the garbage collector will take a while to get to them otherwise. - GC.Collect(); - } - - // Reset the filter text, otherwise the results will be outdated. - FilterText = string.Empty; - - // We want to show the catalog regardless of whether an exception was thrown so that the user has the chance to refresh it. - LoadingCatalogControlVisibility = Visibility.Collapsed; - - StartFilter(); - } - - private static bool IsCriticalException(Exception ex) { - return ex is StackOverflowException || - ex is OutOfMemoryException || - ex is ThreadAbortException || - ex is AccessViolationException; - } - - public void LoadCatalog() { - LoadCatalog(false); - } - - public void RefreshCatalog() { - LoadCatalog(true); - } - - public Visibility CatalogControlVisibility { - get { return _catalogControlVisibility; } - set { - _catalogControlVisibility = value; - OnPropertyChanged(); - } - } - - #endregion - - #region Filtering - public FilterState PackageFilterState { - get { - if (IsFiltering) { - return FilterState.Filtering; - } - if (string.IsNullOrEmpty(FilterText)) { - return FilterState.NoFilterText; - } - if (!FilteredPackages.Any()) { - return FilterState.NoResults; - } - return FilterState.ResultsAvailable; - } - } - - private bool IsFiltering { - get { return _isFiltering; } - set { - _isFiltering = value; - OnPropertyChanged(); - OnPropertyChanged("PackageFilterState"); - } - } - - public IList FilteredPackages { - get { - lock (_filteredPackagesLock) { - return _filteredPackages; - } - } - set { - lock (_filteredPackagesLock) { - _filteredPackages = value; - } - - // PackageFilterState should be triggered before FilteredPackages - // to allow the UI to update the status before the package list, - // making for a smoother experience. - OnPropertyChanged("PackageFilterState"); - OnPropertyChanged(); - } - } - - public string FilterText { - get { return _filterText; } - set { - _filterText = value; - - StartFilter(); - IsFiltering = !string.IsNullOrWhiteSpace(_filterText); - - OnPropertyChanged(); - OnPropertyChanged("PackageFilterState"); - } - } - - private void StartFilter() { - _filterTimer.Change(300, Timeout.Infinite); - } - - private async void FilterTimer_Elapsed(object state) { - if (_allPackages == null) { - LastRefreshedMessage = LastRefreshedMessageProvider.RefreshFailed; - IsFiltering = false; - return; - } - - var filterText = GetTrimmedTextSafe(_filterText); - - IEnumerable filtered; - if (string.IsNullOrWhiteSpace(filterText)) { - filtered = Enumerable.Empty(); - } else { - try { - filtered = await _allPackages.GetCatalogPackagesAsync(filterText); - } catch (Exception ex) { - LastRefreshedMessage = LastRefreshedMessageProvider.RefreshFailed; - if (IsCriticalException(ex)) { - throw; - } - StartFilter(); - return; - } - } - - if (filtered == null) { - // The database file must be in use. Display current results, but try again later. - LastRefreshedMessage = LastRefreshedMessageProvider.RefreshInProgress; - StartFilter(); - return; - } - - var newItems = new List(); - if (filterText != GetTrimmedTextSafe(_filterText)) { - return; - } - - if (filtered.Any()) { - IRootPackage rootPackage = null; - IGlobalPackages globalPackages = null; - var controller = _npmController; - if (controller != null) { - rootPackage = controller.RootPackage; - globalPackages = controller.GlobalPackages; - } - - newItems.AddRange(filtered.Select(package => new ReadOnlyPackageCatalogEntryViewModel( - package, - rootPackage != null ? rootPackage.Modules[package.Name] : null, - globalPackages != null ? globalPackages.Modules[package.Name] : null - ))); - } - - await _dispatcher.BeginInvoke((Action)(() => { - if (filterText != GetTrimmedTextSafe(_filterText)) { - return; - } - - var originalSelectedPackage = SelectedPackage; - FilteredPackages = newItems; - - // Reassign originalSelectedPackage to the original selected package in the new list of filtered packages. - if (originalSelectedPackage != null) { - originalSelectedPackage = FilteredPackages.FirstOrDefault(package => package.Name == originalSelectedPackage.Name); - } - - // Maintain selection when the filter list refreshes (e.g. due to an installation running in the background) - SelectedPackage = originalSelectedPackage ?? FilteredPackages.FirstOrDefault(); - - _currentFilter = filterText; - - LastRefreshedMessage = IsCatalogEmpty - ? LastRefreshedMessageProvider.RefreshFailed - : new LastRefreshedMessageProvider(_allPackages.LastRefreshed); - CatalogControlVisibility = Visibility.Visible; - })); - - IsFiltering = false; - - // The catalog refresh operation spawns many long-lived Gen 2 objects, - // so the garbage collector will take a while to get to them otherwise. - GC.Collect(); - } - - private string GetTrimmedTextSafe(string text) { - return text != null ? text.Trim() : string.Empty; - } - - public LastRefreshedMessageProvider LastRefreshedMessage { - get { return _lastRefreshedMessage; } - set { - _lastRefreshedMessage = value; - OnPropertyChanged(); - } - } - - public Visibility FilterControlsVisibility { - get { return LoadingCatalogControlVisibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; } - } - - #endregion - - #region Installation - - public int SelectedDependencyTypeIndex { - get { return _selectedDependencyTypeIndex; } - set { - _selectedDependencyTypeIndex = value; - OnPropertyChanged(); - OnPropertyChanged("GlobalWarningVisibility"); - } - } - - public object SelectedVersion { - get { return _selectedVersion; } - set { - _selectedVersion = value; - OnPropertyChanged(); - } - } - - public Visibility GlobalWarningVisibility { - get { - return Indices.IndexGlobal == (Indices) SelectedDependencyTypeIndex - ? Visibility.Visible - : Visibility.Collapsed; - } - } - - internal bool CanInstall(PackageCatalogEntryViewModel package) { - return package != null; - } - - internal void Install(PackageCatalogEntryViewModel package) { - var type = DependencyType.Standard; - var isGlobal = false; - switch ((Indices)SelectedDependencyTypeIndex) { - case Indices.IndexDev: - type = DependencyType.Development; - break; - - case Indices.IndexOptional: - type = DependencyType.Optional; - break; - - case Indices.IndexGlobal: - isGlobal = true; - break; - } - - if (!string.IsNullOrEmpty(package.Name)) { - var selectedVersion = SelectedVersion is SemverVersion ? ((SemverVersion)SelectedVersion).ToString(): string.Empty; - _executeViewModel.QueueCommand( - NpmArgumentBuilder.GetNpmInstallArguments( - package.Name, - selectedVersion, - type, - isGlobal, - SaveToPackageJson, - Arguments)); - } - } - - internal bool CanOpenHomepage(string homepage) { - return !string.IsNullOrEmpty(homepage); - } - - internal void OpenHomepage(string homepage) { - if (!string.IsNullOrEmpty(homepage)) { - Process.Start(homepage); - } - } - - public string Arguments { - get { return _arguments; } - set { - _arguments = value; - OnPropertyChanged(); - } - } - - public bool SaveToPackageJson { - get { return _saveToPackageJson; } - set { - _saveToPackageJson = value; - OnPropertyChanged(); - } - } - - public PackageCatalogEntryViewModel SelectedPackage { - get { return _selectedPackage; } - set { - _selectedPackage = value; - OnPropertyChanged(); - } - } - - #endregion - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Windows; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Threading; +using Microsoft.NodejsTools.Npm; +using Microsoft.NodejsTools.Project; + +namespace Microsoft.NodejsTools.NpmUI { + internal class NpmPackageInstallViewModel : INotifyPropertyChanged { + internal enum Indices { + IndexStandard = 0, + IndexDev = 1, + IndexOptional = 2, + IndexGlobal = 3 + } + + internal enum FilterState { + NoFilterText, + Filtering, + ResultsAvailable, + NoResults + } + + public static readonly ICommand InstallCommand = new RoutedCommand(); + public static readonly ICommand OpenHomepageCommand = new RoutedCommand(); + public static readonly ICommand RefreshCatalogCommand = new RoutedCommand(); + + private INpmController _npmController; + + private bool _isFiltering = false; + private bool _isLoadingCatalog; + private IPackageCatalog _allPackages; + private readonly object _filteredPackagesLock = new object(); + private IList _filteredPackages = new List(); + private LastRefreshedMessageProvider _lastRefreshedMessage; + private PackageCatalogEntryViewModel _selectedPackage; + private bool _npmNotFound; + private bool _isCatalogEmpty; + private Visibility _catalogControlVisibility = Visibility.Collapsed; + private string _catalogLoadingMessage = string.Empty; + private string _catalogLoadingProgressMessage = string.Empty; + private Visibility _loadingCatalogControlVisibility = Visibility.Collapsed; + private int _selectedDependencyTypeIndex; + + private string _currentFilter = string.Empty; + private string _filterText = string.Empty; + private readonly Timer _filterTimer; + private string _arguments = string.Empty; + private bool _saveToPackageJson = true; + private object _selectedVersion; + + private readonly Dispatcher _dispatcher; + + private readonly NpmOutputViewModel _executeViewModel; + + public NpmPackageInstallViewModel( + NpmOutputViewModel executeViewModel, + Dispatcher dispatcher + ) { + _dispatcher = dispatcher; + + _executeViewModel = executeViewModel; + _filterTimer = new Timer(FilterTimer_Elapsed, null, Timeout.Infinite, Timeout.Infinite); + } + + public event PropertyChangedEventHandler PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { + PropertyChangedEventHandler handler = PropertyChanged; + if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); + } + + public INpmController NpmController { + get { return _npmController; } + set { + if (null != _npmController) { + _npmController.FinishedRefresh -= NpmController_FinishedRefresh; + } + _npmController = value; + OnPropertyChanged(); + if (null != _npmController) { + LoadCatalog(); + _npmController.FinishedRefresh += NpmController_FinishedRefresh; + } + } + } + + void NpmController_FinishedRefresh(object sender, EventArgs e) { + StartFilter(); + } + + public NpmOutputViewModel ExecuteViewModel { + get { return _executeViewModel; } + } + + #region Catalog control and refresh + public bool IsLoadingCatalog { + get { return _isLoadingCatalog; } + private set { + _isLoadingCatalog = value; + OnPropertyChanged(); + OnPropertyChanged("CanRefreshCatalog"); + } + } + + public bool CanRefreshCatalog { + get { return !IsLoadingCatalog; } + } + + public bool NpmNotFound { + get { return _npmNotFound; } + private set { + _npmNotFound = value; + OnPropertyChanged(); + } + } + + public bool IsCatalogEmpty { + get { return _isCatalogEmpty; } + private set { + _isCatalogEmpty = value; + OnPropertyChanged(); + } + } + + public string LoadingCatalogMessage { + get { return _catalogLoadingMessage; } + private set { + _catalogLoadingMessage = value; + OnPropertyChanged(); + } + } + + public string LoadingCatalogProgressMessage { + get { return _catalogLoadingProgressMessage; } + private set { + _catalogLoadingProgressMessage = value; + OnPropertyChanged(); + } + } + + public Visibility LoadingCatalogControlVisibility { + get { return _loadingCatalogControlVisibility; } + set { + _loadingCatalogControlVisibility = value; + OnPropertyChanged(); + OnPropertyChanged("FilterControlsVisibility"); + } + } + + private async void LoadCatalog(bool forceRefresh) { + IsLoadingCatalog = true; + + CatalogControlVisibility = Visibility.Collapsed; + LoadingCatalogControlVisibility = Visibility.Visible; + LoadingCatalogMessage = SR.GetString(SR.CatalogLoadingDefault); + + LastRefreshedMessage = LastRefreshedMessageProvider.RefreshInProgress; + + var controller = _npmController; + controller.ErrorLogged += _executeViewModel.commander_ErrorLogged; + controller.ExceptionLogged += _executeViewModel.commander_ExceptionLogged; + controller.OutputLogged += _executeViewModel.commander_OutputLogged; + _executeViewModel.SetCancellableSafe(false); + try { + _allPackages = await controller.GetRepositoryCatalogAsync( + forceRefresh, + new Progress(msg => LoadingCatalogProgressMessage = msg) + ); + IsCatalogEmpty = false; + } catch (NpmNotFoundException) { + LastRefreshedMessage = LastRefreshedMessageProvider.NpmNotFound; + } catch (NpmCatalogEmptyException) { + IsCatalogEmpty = true; + LastRefreshedMessage = new LastRefreshedMessageProvider(_allPackages.LastRefreshed); + } catch (Exception ex) { + if (IsCriticalException(ex)) { + throw; + } + + LastRefreshedMessage = LastRefreshedMessageProvider.RefreshFailed; + IsCatalogEmpty = true; + } finally { + IsLoadingCatalog = false; + controller.ErrorLogged -= _executeViewModel.commander_ErrorLogged; + controller.ExceptionLogged -= _executeViewModel.commander_ExceptionLogged; + controller.OutputLogged -= _executeViewModel.commander_OutputLogged; + _executeViewModel.SetCancellableSafe(true); + + // The catalog refresh operation spawns many long-lived Gen 2 objects, + // so the garbage collector will take a while to get to them otherwise. + GC.Collect(); + } + + // Reset the filter text, otherwise the results will be outdated. + FilterText = string.Empty; + + // We want to show the catalog regardless of whether an exception was thrown so that the user has the chance to refresh it. + LoadingCatalogControlVisibility = Visibility.Collapsed; + + StartFilter(); + } + + private static bool IsCriticalException(Exception ex) { + return ex is StackOverflowException || + ex is OutOfMemoryException || + ex is ThreadAbortException || + ex is AccessViolationException; + } + + public void LoadCatalog() { + LoadCatalog(false); + } + + public void RefreshCatalog() { + LoadCatalog(true); + } + + public Visibility CatalogControlVisibility { + get { return _catalogControlVisibility; } + set { + _catalogControlVisibility = value; + OnPropertyChanged(); + } + } + + #endregion + + #region Filtering + public FilterState PackageFilterState { + get { + if (IsFiltering) { + return FilterState.Filtering; + } + if (string.IsNullOrEmpty(FilterText)) { + return FilterState.NoFilterText; + } + if (!FilteredPackages.Any()) { + return FilterState.NoResults; + } + return FilterState.ResultsAvailable; + } + } + + private bool IsFiltering { + get { return _isFiltering; } + set { + _isFiltering = value; + OnPropertyChanged(); + OnPropertyChanged("PackageFilterState"); + } + } + + public IList FilteredPackages { + get { + lock (_filteredPackagesLock) { + return _filteredPackages; + } + } + set { + lock (_filteredPackagesLock) { + _filteredPackages = value; + } + + // PackageFilterState should be triggered before FilteredPackages + // to allow the UI to update the status before the package list, + // making for a smoother experience. + OnPropertyChanged("PackageFilterState"); + OnPropertyChanged(); + } + } + + public string FilterText { + get { return _filterText; } + set { + _filterText = value; + + StartFilter(); + IsFiltering = !string.IsNullOrWhiteSpace(_filterText); + + OnPropertyChanged(); + OnPropertyChanged("PackageFilterState"); + } + } + + private void StartFilter() { + _filterTimer.Change(300, Timeout.Infinite); + } + + private async void FilterTimer_Elapsed(object state) { + if (_allPackages == null) { + LastRefreshedMessage = LastRefreshedMessageProvider.RefreshFailed; + IsFiltering = false; + return; + } + + var filterText = GetTrimmedTextSafe(_filterText); + + IEnumerable filtered; + if (string.IsNullOrWhiteSpace(filterText)) { + filtered = Enumerable.Empty(); + } else { + try { + filtered = await _allPackages.GetCatalogPackagesAsync(filterText); + } catch (Exception ex) { + LastRefreshedMessage = LastRefreshedMessageProvider.RefreshFailed; + if (IsCriticalException(ex)) { + throw; + } + StartFilter(); + return; + } + } + + if (filtered == null) { + // The database file must be in use. Display current results, but try again later. + LastRefreshedMessage = LastRefreshedMessageProvider.RefreshInProgress; + StartFilter(); + return; + } + + var newItems = new List(); + if (filterText != GetTrimmedTextSafe(_filterText)) { + return; + } + + if (filtered.Any()) { + IRootPackage rootPackage = null; + IGlobalPackages globalPackages = null; + var controller = _npmController; + if (controller != null) { + rootPackage = controller.RootPackage; + globalPackages = controller.GlobalPackages; + } + + newItems.AddRange(filtered.Select(package => new ReadOnlyPackageCatalogEntryViewModel( + package, + rootPackage != null ? rootPackage.Modules[package.Name] : null, + globalPackages != null ? globalPackages.Modules[package.Name] : null + ))); + } + + await _dispatcher.BeginInvoke((Action)(() => { + if (filterText != GetTrimmedTextSafe(_filterText)) { + return; + } + + var originalSelectedPackage = SelectedPackage; + FilteredPackages = newItems; + + // Reassign originalSelectedPackage to the original selected package in the new list of filtered packages. + if (originalSelectedPackage != null) { + originalSelectedPackage = FilteredPackages.FirstOrDefault(package => package.Name == originalSelectedPackage.Name); + } + + // Maintain selection when the filter list refreshes (e.g. due to an installation running in the background) + SelectedPackage = originalSelectedPackage ?? FilteredPackages.FirstOrDefault(); + + _currentFilter = filterText; + + LastRefreshedMessage = IsCatalogEmpty + ? LastRefreshedMessageProvider.RefreshFailed + : new LastRefreshedMessageProvider(_allPackages.LastRefreshed); + CatalogControlVisibility = Visibility.Visible; + })); + + IsFiltering = false; + + // The catalog refresh operation spawns many long-lived Gen 2 objects, + // so the garbage collector will take a while to get to them otherwise. + GC.Collect(); + } + + private string GetTrimmedTextSafe(string text) { + return text != null ? text.Trim() : string.Empty; + } + + public LastRefreshedMessageProvider LastRefreshedMessage { + get { return _lastRefreshedMessage; } + set { + _lastRefreshedMessage = value; + OnPropertyChanged(); + } + } + + public Visibility FilterControlsVisibility { + get { return LoadingCatalogControlVisibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; } + } + + #endregion + + #region Installation + + public int SelectedDependencyTypeIndex { + get { return _selectedDependencyTypeIndex; } + set { + _selectedDependencyTypeIndex = value; + OnPropertyChanged(); + OnPropertyChanged("GlobalWarningVisibility"); + } + } + + public object SelectedVersion { + get { return _selectedVersion; } + set { + _selectedVersion = value; + OnPropertyChanged(); + } + } + + public Visibility GlobalWarningVisibility { + get { + return Indices.IndexGlobal == (Indices) SelectedDependencyTypeIndex + ? Visibility.Visible + : Visibility.Collapsed; + } + } + + internal bool CanInstall(PackageCatalogEntryViewModel package) { + return package != null; + } + + internal void Install(PackageCatalogEntryViewModel package) { + var type = DependencyType.Standard; + var isGlobal = false; + switch ((Indices)SelectedDependencyTypeIndex) { + case Indices.IndexDev: + type = DependencyType.Development; + break; + + case Indices.IndexOptional: + type = DependencyType.Optional; + break; + + case Indices.IndexGlobal: + isGlobal = true; + break; + } + + if (!string.IsNullOrEmpty(package.Name)) { + var selectedVersion = SelectedVersion is SemverVersion ? ((SemverVersion)SelectedVersion).ToString(): string.Empty; + _executeViewModel.QueueCommand( + NpmArgumentBuilder.GetNpmInstallArguments( + package.Name, + selectedVersion, + type, + isGlobal, + SaveToPackageJson, + Arguments)); + } + } + + internal bool CanOpenHomepage(string homepage) { + return !string.IsNullOrEmpty(homepage); + } + + internal void OpenHomepage(string homepage) { + if (!string.IsNullOrEmpty(homepage)) { + Process.Start(homepage); + } + } + + public string Arguments { + get { return _arguments; } + set { + _arguments = value; + OnPropertyChanged(); + } + } + + public bool SaveToPackageJson { + get { return _saveToPackageJson; } + set { + _saveToPackageJson = value; + OnPropertyChanged(); + } + } + + public PackageCatalogEntryViewModel SelectedPackage { + get { return _selectedPackage; } + set { + _selectedPackage = value; + OnPropertyChanged(); + } + } + + #endregion + } +} diff --git a/Nodejs/Product/Nodejs/NpmUI/NpmPackageInstallWindow.xaml.cs b/Nodejs/Product/Nodejs/NpmUI/NpmPackageInstallWindow.xaml.cs index a2bbb00c7..8a97ee03c 100644 --- a/Nodejs/Product/Nodejs/NpmUI/NpmPackageInstallWindow.xaml.cs +++ b/Nodejs/Product/Nodejs/NpmUI/NpmPackageInstallWindow.xaml.cs @@ -1,173 +1,173 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Diagnostics; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Threading; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudioTools; - -namespace Microsoft.NodejsTools.NpmUI { - /// - /// Interaction logic for NpmPackageInstallWindow.xaml - /// - sealed partial class NpmPackageInstallWindow : DialogWindowVersioningWorkaround, IDisposable { - private readonly NpmPackageInstallViewModel _vm; - private NpmOutputWindow _outputWindow; - - internal NpmPackageInstallWindow(INpmController controller, NpmOutputViewModel executeVm, DependencyType dependencyType = DependencyType.Standard, bool isGlobal = false) { - DataContext = _vm = new NpmPackageInstallViewModel(executeVm, Dispatcher); - _vm.NpmController = controller; - InitializeComponent(); - DependencyComboBox.SelectedIndex = isGlobal ? (int)NpmPackageInstallViewModel.Indices.IndexGlobal : (int)dependencyType; - } - - public void Dispose() { - // This will unregister event handlers on the controller and prevent - // us from leaking view models. - if (_outputWindow != null) { - _outputWindow.Closing -= _outputWindow_Closing; - _outputWindow.Close(); - } - - _vm.NpmController = null; - - // The catalog refresh operation spawns many long-lived Gen 2 objects, - // so the garbage collector will take a while to get to them otherwise. - GC.Collect(); - } - - void _outputWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { - e.Cancel = true; - _outputWindow.Hide(); - } - - private void Close_Executed(object sender, ExecutedRoutedEventArgs e) { - Close(); - } - - private void Close_CanExecute(object sender, CanExecuteRoutedEventArgs e) { - e.CanExecute = true; - e.Handled = true; - } - - private void InstallCommand_Executed(object sender, ExecutedRoutedEventArgs e) { - _vm.Install(e.Parameter as PackageCatalogEntryViewModel); - } - - private void InstallCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { - e.CanExecute = !FilterTextBox.IsFocused && _vm.CanInstall(e.Parameter as PackageCatalogEntryViewModel); - e.Handled = true; - } - - private void RefreshCatalogCommand_Executed(object sender, ExecutedRoutedEventArgs e) { - _vm.RefreshCatalog(); - } - - private void RefreshCatalogCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { - e.CanExecute = _vm.CanRefreshCatalog; - e.Handled = true; - } - - private void OpenHomepageCommand_Executed(object sender, ExecutedRoutedEventArgs e) { - _vm.OpenHomepage(e.Parameter as string); - } - - private void OpenHomepageCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { - e.CanExecute = _vm.CanOpenHomepage(e.Parameter as string); - e.Handled = true; - } - - private void FilterTextBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { - if ((e.NewValue as bool?) ?? false) { - ((UIElement)sender).Focus(); - } - } - - private void FilterTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { - switch (e.Key) { - case Key.Down: - case Key.Enter: - if (_packageList.SelectedIndex == -1 && _packageList.Items.Count > 0) { - _packageList.SelectedIndex = 0; - } - - FocusOnSelectedItemInPackageList(); - e.Handled = true; - break; - } - } - - private void FocusOnSelectedItemInPackageList() { - _packageList.ScrollIntoView(_packageList.SelectedItem); - var itemContainer = (ListViewItem)_packageList.ItemContainerGenerator.ContainerFromItem(_packageList.SelectedItem); - if (itemContainer != null) { - itemContainer.Focus(); - } - } - - private void _packageList_SelectionChanged(object sender, SelectionChangedEventArgs e) { - _packageList.ScrollIntoView(_packageList.SelectedItem); - } - - private void _packageList_PreviewKeyDown(object sender, KeyEventArgs e) { - if (e.Key == Key.Up && _packageList.SelectedIndex == 0) { - FilterTextBox.Focus(); - e.Handled = true; - } - } - - private void ShowOutputWindow_Click(object sender, RoutedEventArgs e) { - if (_outputWindow == null) { - _outputWindow = new NpmOutputWindow() { - Owner = this, - WindowStartupLocation = System.Windows.WindowStartupLocation.Manual - }; - - _outputWindow.Left = Math.Max(0, this.Left - _outputWindow.Width - 30); - _outputWindow.Top = Math.Max(0, this.Top); - - _outputWindow.Closing += _outputWindow_Closing; - _outputWindow.DataContext = _vm.ExecuteViewModel; - } - - _outputWindow.Show(); - if (_outputWindow.WindowState == WindowState.Minimized) { - _outputWindow.WindowState = WindowState.Normal; - } - } - - private void ResetOptionsButton_Click(object sender, RoutedEventArgs e) { - this.DependencyComboBox.SelectedIndex = (int)DependencyType.Standard; - this.SaveToPackageJsonCheckbox.IsChecked = true; - - ArgumentsTextBox.Text = string.Empty; - ArgumentsTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); - } - - private void SelectedVersionComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { - if (this.SelectedVersionComboBox.SelectedIndex == -1) { - SelectedVersionComboBox.SelectedIndex = 0; - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudioTools; + +namespace Microsoft.NodejsTools.NpmUI { + /// + /// Interaction logic for NpmPackageInstallWindow.xaml + /// + sealed partial class NpmPackageInstallWindow : DialogWindowVersioningWorkaround, IDisposable { + private readonly NpmPackageInstallViewModel _vm; + private NpmOutputWindow _outputWindow; + + internal NpmPackageInstallWindow(INpmController controller, NpmOutputViewModel executeVm, DependencyType dependencyType = DependencyType.Standard, bool isGlobal = false) { + DataContext = _vm = new NpmPackageInstallViewModel(executeVm, Dispatcher); + _vm.NpmController = controller; + InitializeComponent(); + DependencyComboBox.SelectedIndex = isGlobal ? (int)NpmPackageInstallViewModel.Indices.IndexGlobal : (int)dependencyType; + } + + public void Dispose() { + // This will unregister event handlers on the controller and prevent + // us from leaking view models. + if (_outputWindow != null) { + _outputWindow.Closing -= _outputWindow_Closing; + _outputWindow.Close(); + } + + _vm.NpmController = null; + + // The catalog refresh operation spawns many long-lived Gen 2 objects, + // so the garbage collector will take a while to get to them otherwise. + GC.Collect(); + } + + void _outputWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) { + e.Cancel = true; + _outputWindow.Hide(); + } + + private void Close_Executed(object sender, ExecutedRoutedEventArgs e) { + Close(); + } + + private void Close_CanExecute(object sender, CanExecuteRoutedEventArgs e) { + e.CanExecute = true; + e.Handled = true; + } + + private void InstallCommand_Executed(object sender, ExecutedRoutedEventArgs e) { + _vm.Install(e.Parameter as PackageCatalogEntryViewModel); + } + + private void InstallCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { + e.CanExecute = !FilterTextBox.IsFocused && _vm.CanInstall(e.Parameter as PackageCatalogEntryViewModel); + e.Handled = true; + } + + private void RefreshCatalogCommand_Executed(object sender, ExecutedRoutedEventArgs e) { + _vm.RefreshCatalog(); + } + + private void RefreshCatalogCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { + e.CanExecute = _vm.CanRefreshCatalog; + e.Handled = true; + } + + private void OpenHomepageCommand_Executed(object sender, ExecutedRoutedEventArgs e) { + _vm.OpenHomepage(e.Parameter as string); + } + + private void OpenHomepageCommand_CanExecute(object sender, CanExecuteRoutedEventArgs e) { + e.CanExecute = _vm.CanOpenHomepage(e.Parameter as string); + e.Handled = true; + } + + private void FilterTextBox_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e) { + if ((e.NewValue as bool?) ?? false) { + ((UIElement)sender).Focus(); + } + } + + private void FilterTextBox_PreviewKeyDown(object sender, KeyEventArgs e) { + switch (e.Key) { + case Key.Down: + case Key.Enter: + if (_packageList.SelectedIndex == -1 && _packageList.Items.Count > 0) { + _packageList.SelectedIndex = 0; + } + + FocusOnSelectedItemInPackageList(); + e.Handled = true; + break; + } + } + + private void FocusOnSelectedItemInPackageList() { + _packageList.ScrollIntoView(_packageList.SelectedItem); + var itemContainer = (ListViewItem)_packageList.ItemContainerGenerator.ContainerFromItem(_packageList.SelectedItem); + if (itemContainer != null) { + itemContainer.Focus(); + } + } + + private void _packageList_SelectionChanged(object sender, SelectionChangedEventArgs e) { + _packageList.ScrollIntoView(_packageList.SelectedItem); + } + + private void _packageList_PreviewKeyDown(object sender, KeyEventArgs e) { + if (e.Key == Key.Up && _packageList.SelectedIndex == 0) { + FilterTextBox.Focus(); + e.Handled = true; + } + } + + private void ShowOutputWindow_Click(object sender, RoutedEventArgs e) { + if (_outputWindow == null) { + _outputWindow = new NpmOutputWindow() { + Owner = this, + WindowStartupLocation = System.Windows.WindowStartupLocation.Manual + }; + + _outputWindow.Left = Math.Max(0, this.Left - _outputWindow.Width - 30); + _outputWindow.Top = Math.Max(0, this.Top); + + _outputWindow.Closing += _outputWindow_Closing; + _outputWindow.DataContext = _vm.ExecuteViewModel; + } + + _outputWindow.Show(); + if (_outputWindow.WindowState == WindowState.Minimized) { + _outputWindow.WindowState = WindowState.Normal; + } + } + + private void ResetOptionsButton_Click(object sender, RoutedEventArgs e) { + this.DependencyComboBox.SelectedIndex = (int)DependencyType.Standard; + this.SaveToPackageJsonCheckbox.IsChecked = true; + + ArgumentsTextBox.Text = string.Empty; + ArgumentsTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource(); + } + + private void SelectedVersionComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { + if (this.SelectedVersionComboBox.SelectedIndex == -1) { + SelectedVersionComboBox.SelectedIndex = 0; + } + } + } +} diff --git a/Nodejs/Product/Nodejs/NpmUI/PackageCatalogEntryViewModel.cs b/Nodejs/Product/Nodejs/NpmUI/PackageCatalogEntryViewModel.cs index e36dc2008..049e246be 100644 --- a/Nodejs/Product/Nodejs/NpmUI/PackageCatalogEntryViewModel.cs +++ b/Nodejs/Product/Nodejs/NpmUI/PackageCatalogEntryViewModel.cs @@ -1,141 +1,141 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using Microsoft.NodejsTools.Npm; -using Microsoft.NodejsTools.Project; - -namespace Microsoft.NodejsTools.NpmUI { - abstract class PackageCatalogEntryViewModel { - private readonly string _name; - private readonly SemverVersion? _version; - private readonly List _availableVersions; - private readonly string _author; - private readonly string _description; - private readonly List _homepages; - private readonly string _keywords; - - private readonly SemverVersion? _localVersion, _globalVersion; - - protected PackageCatalogEntryViewModel( - string name, - SemverVersion? version, - IEnumerable availableVersions, - string author, - string description, - IEnumerable homepages, - string keywords, - SemverVersion? localVersion, - SemverVersion? globalVersion - ) { - _name = name; - _version = version; - _availableVersions = availableVersions != null ? availableVersions.ToList() : new List(); - _author = author; - _description = description; - _homepages = homepages != null ? homepages.ToList() : new List(); - _keywords = keywords; - _localVersion = localVersion; - _globalVersion = globalVersion; - } - - public virtual string Name { - get { return _name; } - } - - public string Version { - get { return ToString(_version); } - } - - public IEnumerable AvailableVersions { - get { return _availableVersions; } - } - - public string Author { - get { return _author; } - } - - public Visibility AuthorVisibility { - get { return string.IsNullOrEmpty(_author) ? Visibility.Collapsed : Visibility.Visible; } - } - - public string Description { get { return _description; } } - - public Visibility DescriptionVisibility { get { return string.IsNullOrEmpty(_description) ? Visibility.Collapsed : Visibility.Visible; } } - - public IEnumerable Homepages { get { return _homepages; } } - - public Visibility HomepagesVisibility { - get { return _homepages.Any() ? Visibility.Visible : Visibility.Collapsed; } - } - - public string Keywords { - get { return _keywords; } - } - - public bool IsInstalledLocally { - get { return _localVersion.HasValue; } - } - - public bool IsInstalledGlobally { - get { return _globalVersion.HasValue; } - } - - public bool IsLocalInstallOutOfDate { - get { return _localVersion.HasValue && _localVersion < _version; } - } - - public bool IsGlobalInstallOutOfDate { - get { return _globalVersion.HasValue && _globalVersion < _version; } - } - - public string LocalVersion { - get { return ToString(_localVersion); } - } - - public string GlobalVersion { - get { return ToString(_globalVersion); } - } - - private static string ToString(SemverVersion? version) { - return version.HasValue ? version.ToString() : string.Empty; - } - } - - internal class ReadOnlyPackageCatalogEntryViewModel : PackageCatalogEntryViewModel { - public ReadOnlyPackageCatalogEntryViewModel(IPackage package, IPackage localInstall, IPackage globalInstall) - : base( - package.Name ?? string.Empty, - package.Version, - package.AvailableVersions, - package.Author == null ? string.Empty : package.Author.ToString(), - package.Description ?? string.Empty, - package.Homepages, - (package.Keywords != null && package.Keywords.Any()) - ? string.Join(", ", package.Keywords) - : SR.GetString(SR.NoKeywordsInPackage), - localInstall != null ? (SemverVersion?)localInstall.Version : null, - globalInstall != null ? (SemverVersion?)globalInstall.Version : null - ) { - if (string.IsNullOrEmpty(Name)) { - throw new ArgumentNullException("package.Name"); - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows; +using Microsoft.NodejsTools.Npm; +using Microsoft.NodejsTools.Project; + +namespace Microsoft.NodejsTools.NpmUI { + abstract class PackageCatalogEntryViewModel { + private readonly string _name; + private readonly SemverVersion? _version; + private readonly List _availableVersions; + private readonly string _author; + private readonly string _description; + private readonly List _homepages; + private readonly string _keywords; + + private readonly SemverVersion? _localVersion, _globalVersion; + + protected PackageCatalogEntryViewModel( + string name, + SemverVersion? version, + IEnumerable availableVersions, + string author, + string description, + IEnumerable homepages, + string keywords, + SemverVersion? localVersion, + SemverVersion? globalVersion + ) { + _name = name; + _version = version; + _availableVersions = availableVersions != null ? availableVersions.ToList() : new List(); + _author = author; + _description = description; + _homepages = homepages != null ? homepages.ToList() : new List(); + _keywords = keywords; + _localVersion = localVersion; + _globalVersion = globalVersion; + } + + public virtual string Name { + get { return _name; } + } + + public string Version { + get { return ToString(_version); } + } + + public IEnumerable AvailableVersions { + get { return _availableVersions; } + } + + public string Author { + get { return _author; } + } + + public Visibility AuthorVisibility { + get { return string.IsNullOrEmpty(_author) ? Visibility.Collapsed : Visibility.Visible; } + } + + public string Description { get { return _description; } } + + public Visibility DescriptionVisibility { get { return string.IsNullOrEmpty(_description) ? Visibility.Collapsed : Visibility.Visible; } } + + public IEnumerable Homepages { get { return _homepages; } } + + public Visibility HomepagesVisibility { + get { return _homepages.Any() ? Visibility.Visible : Visibility.Collapsed; } + } + + public string Keywords { + get { return _keywords; } + } + + public bool IsInstalledLocally { + get { return _localVersion.HasValue; } + } + + public bool IsInstalledGlobally { + get { return _globalVersion.HasValue; } + } + + public bool IsLocalInstallOutOfDate { + get { return _localVersion.HasValue && _localVersion < _version; } + } + + public bool IsGlobalInstallOutOfDate { + get { return _globalVersion.HasValue && _globalVersion < _version; } + } + + public string LocalVersion { + get { return ToString(_localVersion); } + } + + public string GlobalVersion { + get { return ToString(_globalVersion); } + } + + private static string ToString(SemverVersion? version) { + return version.HasValue ? version.ToString() : string.Empty; + } + } + + internal class ReadOnlyPackageCatalogEntryViewModel : PackageCatalogEntryViewModel { + public ReadOnlyPackageCatalogEntryViewModel(IPackage package, IPackage localInstall, IPackage globalInstall) + : base( + package.Name ?? string.Empty, + package.Version, + package.AvailableVersions, + package.Author == null ? string.Empty : package.Author.ToString(), + package.Description ?? string.Empty, + package.Homepages, + (package.Keywords != null && package.Keywords.Any()) + ? string.Join(", ", package.Keywords) + : SR.GetString(SR.NoKeywordsInPackage), + localInstall != null ? (SemverVersion?)localInstall.Version : null, + globalInstall != null ? (SemverVersion?)globalInstall.Version : null + ) { + if (string.IsNullOrEmpty(Name)) { + throw new ArgumentNullException("package.Name"); + } + } + } +} diff --git a/Nodejs/Product/Nodejs/Options/NodejsDiagnosticsOptionsPage.cs b/Nodejs/Product/Nodejs/Options/NodejsDiagnosticsOptionsPage.cs index 61e779743..f78c1f5bf 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsDiagnosticsOptionsPage.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsDiagnosticsOptionsPage.cs @@ -12,25 +12,25 @@ // implied. See the License for the specific language governing // permissions and limitations under the License. // -//*********************************************************// - -namespace Microsoft.NodejsTools.Options { - internal class NodejsDiagnosticsOptionsPage : NodejsDialogPage { - private bool _isLiveDiagnosticsEnabled; - private const string IsLiveDiagnosticsEnabledSetting = "IsLiveDiagnosticsEnabled"; - - public NodejsDiagnosticsOptionsPage() : base("Diagnostics") { - _isLiveDiagnosticsEnabled = !NodejsPackage.Instance.Zombied && (LoadBool(IsLiveDiagnosticsEnabledSetting) ?? false); - } - - public bool IsLiveDiagnosticsEnabled { - get { - return !NodejsPackage.Instance.Zombied && _isLiveDiagnosticsEnabled; - } - set { - _isLiveDiagnosticsEnabled = value; - SaveBool(IsLiveDiagnosticsEnabledSetting, value); - } - } - } -} +//*********************************************************// + +namespace Microsoft.NodejsTools.Options { + internal class NodejsDiagnosticsOptionsPage : NodejsDialogPage { + private bool _isLiveDiagnosticsEnabled; + private const string IsLiveDiagnosticsEnabledSetting = "IsLiveDiagnosticsEnabled"; + + public NodejsDiagnosticsOptionsPage() : base("Diagnostics") { + _isLiveDiagnosticsEnabled = !NodejsPackage.Instance.Zombied && (LoadBool(IsLiveDiagnosticsEnabledSetting) ?? false); + } + + public bool IsLiveDiagnosticsEnabled { + get { + return !NodejsPackage.Instance.Zombied && _isLiveDiagnosticsEnabled; + } + set { + _isLiveDiagnosticsEnabled = value; + SaveBool(IsLiveDiagnosticsEnabledSetting, value); + } + } + } +} diff --git a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsControl.Designer.cs b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsControl.Designer.cs index 212006d76..8004732e6 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsControl.Designer.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsControl.Designer.cs @@ -1,193 +1,193 @@ -namespace Microsoft.NodejsTools.Options { - partial class NodejsGeneralOptionsControl { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) { - if (disposing && (components != null)) { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() { - this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); - this._surveyNewsCheckLabel = new System.Windows.Forms.Label(); - this._surveyNewsCheckCombo = new System.Windows.Forms.ComboBox(); - this._topOptionsPanel = new System.Windows.Forms.Panel(); - this._checkForLongPaths = new System.Windows.Forms.CheckBox(); - this._editAndContinue = new System.Windows.Forms.CheckBox(); - this._waitOnNormalExit = new System.Windows.Forms.CheckBox(); - this._waitOnAbnormalExit = new System.Windows.Forms.CheckBox(); - this._showBrowserAndNodeLabels = new System.Windows.Forms.CheckBox(); - this.tableLayoutPanel3.SuspendLayout(); - this._topOptionsPanel.SuspendLayout(); - this.SuspendLayout(); - // - // tableLayoutPanel3 - // - this.tableLayoutPanel3.AutoSize = true; - this.tableLayoutPanel3.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; - this.tableLayoutPanel3.ColumnCount = 2; - this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); - this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); - this.tableLayoutPanel3.Controls.Add(this._surveyNewsCheckLabel, 0, 7); - this.tableLayoutPanel3.Controls.Add(this._surveyNewsCheckCombo, 1, 7); - this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; - this.tableLayoutPanel3.Location = new System.Drawing.Point(0, 149); - this.tableLayoutPanel3.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); - this.tableLayoutPanel3.Name = "tableLayoutPanel3"; - this.tableLayoutPanel3.RowCount = 9; - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); - this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25F)); - this.tableLayoutPanel3.Size = new System.Drawing.Size(508, 208); - this.tableLayoutPanel3.TabIndex = 0; - // - // _surveyNewsCheckLabel - // - this._surveyNewsCheckLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; - this._surveyNewsCheckLabel.AutoSize = true; - this._surveyNewsCheckLabel.Location = new System.Drawing.Point(8, 7); - this._surveyNewsCheckLabel.Margin = new System.Windows.Forms.Padding(8, 0, 8, 0); - this._surveyNewsCheckLabel.Name = "_surveyNewsCheckLabel"; - this._surveyNewsCheckLabel.Size = new System.Drawing.Size(150, 17); - this._surveyNewsCheckLabel.TabIndex = 7; - this._surveyNewsCheckLabel.Text = "&Check for survey/news"; - this._surveyNewsCheckLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; - // - // _surveyNewsCheckCombo - // - this._surveyNewsCheckCombo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); - this._surveyNewsCheckCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this._surveyNewsCheckCombo.DropDownWidth = 172; - this._surveyNewsCheckCombo.FormattingEnabled = true; - this._surveyNewsCheckCombo.Items.AddRange(new object[] { - "Never", - "Once a day", - "Once a week", - "Once a month"}); - this._surveyNewsCheckCombo.Location = new System.Drawing.Point(174, 4); - this._surveyNewsCheckCombo.Margin = new System.Windows.Forms.Padding(8, 4, 8, 4); - this._surveyNewsCheckCombo.Name = "_surveyNewsCheckCombo"; - this._surveyNewsCheckCombo.Size = new System.Drawing.Size(326, 24); - this._surveyNewsCheckCombo.TabIndex = 8; - // - // _topOptionsPanel - // - this._topOptionsPanel.Controls.Add(this._showBrowserAndNodeLabels); - this._topOptionsPanel.Controls.Add(this._checkForLongPaths); - this._topOptionsPanel.Controls.Add(this._editAndContinue); - this._topOptionsPanel.Controls.Add(this._waitOnNormalExit); - this._topOptionsPanel.Controls.Add(this._waitOnAbnormalExit); - this._topOptionsPanel.Dock = System.Windows.Forms.DockStyle.Top; - this._topOptionsPanel.Location = new System.Drawing.Point(0, 0); - this._topOptionsPanel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this._topOptionsPanel.Name = "_topOptionsPanel"; - this._topOptionsPanel.Size = new System.Drawing.Size(508, 149); - this._topOptionsPanel.TabIndex = 1; - // - // _checkForLongPaths - // - this._checkForLongPaths.AutoSize = true; - this._checkForLongPaths.Location = new System.Drawing.Point(5, 90); - this._checkForLongPaths.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this._checkForLongPaths.Name = "_checkForLongPaths"; - this._checkForLongPaths.Size = new System.Drawing.Size(379, 21); - this._checkForLongPaths.TabIndex = 4; - this._checkForLongPaths.Text = "Check for paths that exceed the &MAX_PATH length limit"; - this._checkForLongPaths.UseVisualStyleBackColor = true; - // - // _editAndContinue - // - this._editAndContinue.AutoSize = true; - this._editAndContinue.Location = new System.Drawing.Point(5, 62); - this._editAndContinue.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this._editAndContinue.Name = "_editAndContinue"; - this._editAndContinue.Size = new System.Drawing.Size(190, 21); - this._editAndContinue.TabIndex = 3; - this._editAndContinue.Text = "Enable &Edit and Continue"; - this._editAndContinue.UseVisualStyleBackColor = true; - // - // _waitOnNormalExit - // - this._waitOnNormalExit.AutoSize = true; - this._waitOnNormalExit.Location = new System.Drawing.Point(5, 33); - this._waitOnNormalExit.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this._waitOnNormalExit.Name = "_waitOnNormalExit"; - this._waitOnNormalExit.Size = new System.Drawing.Size(294, 21); - this._waitOnNormalExit.TabIndex = 2; - this._waitOnNormalExit.Text = "Wai&t for input when process exits normally"; - this._waitOnNormalExit.UseVisualStyleBackColor = true; - // - // _waitOnAbnormalExit - // - this._waitOnAbnormalExit.AutoSize = true; - this._waitOnAbnormalExit.Location = new System.Drawing.Point(5, 5); - this._waitOnAbnormalExit.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); - this._waitOnAbnormalExit.Name = "_waitOnAbnormalExit"; - this._waitOnAbnormalExit.Size = new System.Drawing.Size(310, 21); - this._waitOnAbnormalExit.TabIndex = 1; - this._waitOnAbnormalExit.Text = "&Wait for input when process exits abnormally"; - this._waitOnAbnormalExit.UseVisualStyleBackColor = true; - // - // _showBrowserAndNodeLabels - // - this._showBrowserAndNodeLabels.AutoSize = true; - this._showBrowserAndNodeLabels.Location = new System.Drawing.Point(5, 119); - this._showBrowserAndNodeLabels.Margin = new System.Windows.Forms.Padding(4); - this._showBrowserAndNodeLabels.Name = "_showBrowserAndNodeLabels"; - this._showBrowserAndNodeLabels.Size = new System.Drawing.Size(471, 21); - this._showBrowserAndNodeLabels.TabIndex = 6; - this._showBrowserAndNodeLabels.Text = "Show &labels denoting browser and Node.js code in Solution Explorer"; - this._showBrowserAndNodeLabels.UseVisualStyleBackColor = true; - // - // NodejsGeneralOptionsControl - // - this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.tableLayoutPanel3); - this.Controls.Add(this._topOptionsPanel); - this.Margin = new System.Windows.Forms.Padding(8, 10, 8, 10); - this.Name = "NodejsGeneralOptionsControl"; - this.Size = new System.Drawing.Size(508, 357); - this.tableLayoutPanel3.ResumeLayout(false); - this.tableLayoutPanel3.PerformLayout(); - this._topOptionsPanel.ResumeLayout(false); - this._topOptionsPanel.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; - private System.Windows.Forms.Label _surveyNewsCheckLabel; - private System.Windows.Forms.ComboBox _surveyNewsCheckCombo; - private System.Windows.Forms.Panel _topOptionsPanel; - private System.Windows.Forms.CheckBox _waitOnNormalExit; - private System.Windows.Forms.CheckBox _waitOnAbnormalExit; - private System.Windows.Forms.CheckBox _editAndContinue; - private System.Windows.Forms.CheckBox _checkForLongPaths; - private System.Windows.Forms.CheckBox _showBrowserAndNodeLabels; - } -} +namespace Microsoft.NodejsTools.Options { + partial class NodejsGeneralOptionsControl { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this._surveyNewsCheckLabel = new System.Windows.Forms.Label(); + this._surveyNewsCheckCombo = new System.Windows.Forms.ComboBox(); + this._topOptionsPanel = new System.Windows.Forms.Panel(); + this._checkForLongPaths = new System.Windows.Forms.CheckBox(); + this._editAndContinue = new System.Windows.Forms.CheckBox(); + this._waitOnNormalExit = new System.Windows.Forms.CheckBox(); + this._waitOnAbnormalExit = new System.Windows.Forms.CheckBox(); + this._showBrowserAndNodeLabels = new System.Windows.Forms.CheckBox(); + this.tableLayoutPanel3.SuspendLayout(); + this._topOptionsPanel.SuspendLayout(); + this.SuspendLayout(); + // + // tableLayoutPanel3 + // + this.tableLayoutPanel3.AutoSize = true; + this.tableLayoutPanel3.AutoSizeMode = System.Windows.Forms.AutoSizeMode.GrowAndShrink; + this.tableLayoutPanel3.ColumnCount = 2; + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle()); + this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F)); + this.tableLayoutPanel3.Controls.Add(this._surveyNewsCheckLabel, 0, 7); + this.tableLayoutPanel3.Controls.Add(this._surveyNewsCheckCombo, 1, 7); + this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill; + this.tableLayoutPanel3.Location = new System.Drawing.Point(0, 149); + this.tableLayoutPanel3.Margin = new System.Windows.Forms.Padding(4, 5, 4, 5); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + this.tableLayoutPanel3.RowCount = 9; + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle()); + this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 25F)); + this.tableLayoutPanel3.Size = new System.Drawing.Size(508, 208); + this.tableLayoutPanel3.TabIndex = 0; + // + // _surveyNewsCheckLabel + // + this._surveyNewsCheckLabel.Anchor = System.Windows.Forms.AnchorStyles.Left; + this._surveyNewsCheckLabel.AutoSize = true; + this._surveyNewsCheckLabel.Location = new System.Drawing.Point(8, 7); + this._surveyNewsCheckLabel.Margin = new System.Windows.Forms.Padding(8, 0, 8, 0); + this._surveyNewsCheckLabel.Name = "_surveyNewsCheckLabel"; + this._surveyNewsCheckLabel.Size = new System.Drawing.Size(150, 17); + this._surveyNewsCheckLabel.TabIndex = 7; + this._surveyNewsCheckLabel.Text = "&Check for survey/news"; + this._surveyNewsCheckLabel.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; + // + // _surveyNewsCheckCombo + // + this._surveyNewsCheckCombo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right))); + this._surveyNewsCheckCombo.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this._surveyNewsCheckCombo.DropDownWidth = 172; + this._surveyNewsCheckCombo.FormattingEnabled = true; + this._surveyNewsCheckCombo.Items.AddRange(new object[] { + "Never", + "Once a day", + "Once a week", + "Once a month"}); + this._surveyNewsCheckCombo.Location = new System.Drawing.Point(174, 4); + this._surveyNewsCheckCombo.Margin = new System.Windows.Forms.Padding(8, 4, 8, 4); + this._surveyNewsCheckCombo.Name = "_surveyNewsCheckCombo"; + this._surveyNewsCheckCombo.Size = new System.Drawing.Size(326, 24); + this._surveyNewsCheckCombo.TabIndex = 8; + // + // _topOptionsPanel + // + this._topOptionsPanel.Controls.Add(this._showBrowserAndNodeLabels); + this._topOptionsPanel.Controls.Add(this._checkForLongPaths); + this._topOptionsPanel.Controls.Add(this._editAndContinue); + this._topOptionsPanel.Controls.Add(this._waitOnNormalExit); + this._topOptionsPanel.Controls.Add(this._waitOnAbnormalExit); + this._topOptionsPanel.Dock = System.Windows.Forms.DockStyle.Top; + this._topOptionsPanel.Location = new System.Drawing.Point(0, 0); + this._topOptionsPanel.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this._topOptionsPanel.Name = "_topOptionsPanel"; + this._topOptionsPanel.Size = new System.Drawing.Size(508, 149); + this._topOptionsPanel.TabIndex = 1; + // + // _checkForLongPaths + // + this._checkForLongPaths.AutoSize = true; + this._checkForLongPaths.Location = new System.Drawing.Point(5, 90); + this._checkForLongPaths.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this._checkForLongPaths.Name = "_checkForLongPaths"; + this._checkForLongPaths.Size = new System.Drawing.Size(379, 21); + this._checkForLongPaths.TabIndex = 4; + this._checkForLongPaths.Text = "Check for paths that exceed the &MAX_PATH length limit"; + this._checkForLongPaths.UseVisualStyleBackColor = true; + // + // _editAndContinue + // + this._editAndContinue.AutoSize = true; + this._editAndContinue.Location = new System.Drawing.Point(5, 62); + this._editAndContinue.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this._editAndContinue.Name = "_editAndContinue"; + this._editAndContinue.Size = new System.Drawing.Size(190, 21); + this._editAndContinue.TabIndex = 3; + this._editAndContinue.Text = "Enable &Edit and Continue"; + this._editAndContinue.UseVisualStyleBackColor = true; + // + // _waitOnNormalExit + // + this._waitOnNormalExit.AutoSize = true; + this._waitOnNormalExit.Location = new System.Drawing.Point(5, 33); + this._waitOnNormalExit.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this._waitOnNormalExit.Name = "_waitOnNormalExit"; + this._waitOnNormalExit.Size = new System.Drawing.Size(294, 21); + this._waitOnNormalExit.TabIndex = 2; + this._waitOnNormalExit.Text = "Wai&t for input when process exits normally"; + this._waitOnNormalExit.UseVisualStyleBackColor = true; + // + // _waitOnAbnormalExit + // + this._waitOnAbnormalExit.AutoSize = true; + this._waitOnAbnormalExit.Location = new System.Drawing.Point(5, 5); + this._waitOnAbnormalExit.Margin = new System.Windows.Forms.Padding(4, 4, 4, 4); + this._waitOnAbnormalExit.Name = "_waitOnAbnormalExit"; + this._waitOnAbnormalExit.Size = new System.Drawing.Size(310, 21); + this._waitOnAbnormalExit.TabIndex = 1; + this._waitOnAbnormalExit.Text = "&Wait for input when process exits abnormally"; + this._waitOnAbnormalExit.UseVisualStyleBackColor = true; + // + // _showBrowserAndNodeLabels + // + this._showBrowserAndNodeLabels.AutoSize = true; + this._showBrowserAndNodeLabels.Location = new System.Drawing.Point(5, 119); + this._showBrowserAndNodeLabels.Margin = new System.Windows.Forms.Padding(4); + this._showBrowserAndNodeLabels.Name = "_showBrowserAndNodeLabels"; + this._showBrowserAndNodeLabels.Size = new System.Drawing.Size(471, 21); + this._showBrowserAndNodeLabels.TabIndex = 6; + this._showBrowserAndNodeLabels.Text = "Show &labels denoting browser and Node.js code in Solution Explorer"; + this._showBrowserAndNodeLabels.UseVisualStyleBackColor = true; + // + // NodejsGeneralOptionsControl + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.tableLayoutPanel3); + this.Controls.Add(this._topOptionsPanel); + this.Margin = new System.Windows.Forms.Padding(8, 10, 8, 10); + this.Name = "NodejsGeneralOptionsControl"; + this.Size = new System.Drawing.Size(508, 357); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + this._topOptionsPanel.ResumeLayout(false); + this._topOptionsPanel.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.Label _surveyNewsCheckLabel; + private System.Windows.Forms.ComboBox _surveyNewsCheckCombo; + private System.Windows.Forms.Panel _topOptionsPanel; + private System.Windows.Forms.CheckBox _waitOnNormalExit; + private System.Windows.Forms.CheckBox _waitOnAbnormalExit; + private System.Windows.Forms.CheckBox _editAndContinue; + private System.Windows.Forms.CheckBox _checkForLongPaths; + private System.Windows.Forms.CheckBox _showBrowserAndNodeLabels; + } +} diff --git a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsControl.cs b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsControl.cs index 0a074e921..74e87a858 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsControl.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsControl.cs @@ -1,85 +1,85 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Windows.Forms; - -namespace Microsoft.NodejsTools.Options { - public partial class NodejsGeneralOptionsControl : UserControl { - private const int SurveyNewsNeverIndex = 0; - private const int SurveyNewsOnceDayIndex = 1; - private const int SurveyNewsOnceWeekIndex = 2; - private const int SurveyNewsOnceMonthIndex = 3; - - public NodejsGeneralOptionsControl() { - InitializeComponent(); - } - - internal SurveyNewsPolicy SurveyNewsCheckCombo { - get { - switch (_surveyNewsCheckCombo.SelectedIndex) { - case SurveyNewsNeverIndex: - return SurveyNewsPolicy.Disabled; - case SurveyNewsOnceDayIndex: - return SurveyNewsPolicy.CheckOnceDay; - case SurveyNewsOnceWeekIndex: - return SurveyNewsPolicy.CheckOnceWeek; - case SurveyNewsOnceMonthIndex: - return SurveyNewsPolicy.CheckOnceMonth; - default: - return SurveyNewsPolicy.Disabled; - } - } - set { - switch (value) { - case SurveyNewsPolicy.Disabled: - _surveyNewsCheckCombo.SelectedIndex = SurveyNewsNeverIndex; - break; - case SurveyNewsPolicy.CheckOnceDay: - _surveyNewsCheckCombo.SelectedIndex = SurveyNewsOnceDayIndex; - break; - case SurveyNewsPolicy.CheckOnceWeek: - _surveyNewsCheckCombo.SelectedIndex = SurveyNewsOnceWeekIndex; - break; - case SurveyNewsPolicy.CheckOnceMonth: - _surveyNewsCheckCombo.SelectedIndex = SurveyNewsOnceMonthIndex; - break; - } - } - } - - internal void SyncControlWithPageSettings(NodejsGeneralOptionsPage page) { - SurveyNewsCheckCombo = page.SurveyNewsCheck; - _waitOnAbnormalExit.Checked = page.WaitOnAbnormalExit; - _waitOnNormalExit.Checked = page.WaitOnNormalExit; - _editAndContinue.Checked = page.EditAndContinue; - _checkForLongPaths.Checked = page.CheckForLongPaths; - _showBrowserAndNodeLabels.Checked = page.ShowBrowserAndNodeLabels; - - // Disable the show "browser" and "node" labels option when the user is in ES6 IntelliSense Preview mode - _showBrowserAndNodeLabels.Enabled = NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevel != AnalysisLevel.Preview; - } - - internal void SyncPageWithControlSettings(NodejsGeneralOptionsPage page) { - page.SurveyNewsCheck = SurveyNewsCheckCombo; - page.WaitOnAbnormalExit = _waitOnAbnormalExit.Checked; - page.WaitOnNormalExit = _waitOnNormalExit.Checked; - page.EditAndContinue = _editAndContinue.Checked; - page.CheckForLongPaths = _checkForLongPaths.Checked; - page.ShowBrowserAndNodeLabels = _showBrowserAndNodeLabels.Checked; - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Windows.Forms; + +namespace Microsoft.NodejsTools.Options { + public partial class NodejsGeneralOptionsControl : UserControl { + private const int SurveyNewsNeverIndex = 0; + private const int SurveyNewsOnceDayIndex = 1; + private const int SurveyNewsOnceWeekIndex = 2; + private const int SurveyNewsOnceMonthIndex = 3; + + public NodejsGeneralOptionsControl() { + InitializeComponent(); + } + + internal SurveyNewsPolicy SurveyNewsCheckCombo { + get { + switch (_surveyNewsCheckCombo.SelectedIndex) { + case SurveyNewsNeverIndex: + return SurveyNewsPolicy.Disabled; + case SurveyNewsOnceDayIndex: + return SurveyNewsPolicy.CheckOnceDay; + case SurveyNewsOnceWeekIndex: + return SurveyNewsPolicy.CheckOnceWeek; + case SurveyNewsOnceMonthIndex: + return SurveyNewsPolicy.CheckOnceMonth; + default: + return SurveyNewsPolicy.Disabled; + } + } + set { + switch (value) { + case SurveyNewsPolicy.Disabled: + _surveyNewsCheckCombo.SelectedIndex = SurveyNewsNeverIndex; + break; + case SurveyNewsPolicy.CheckOnceDay: + _surveyNewsCheckCombo.SelectedIndex = SurveyNewsOnceDayIndex; + break; + case SurveyNewsPolicy.CheckOnceWeek: + _surveyNewsCheckCombo.SelectedIndex = SurveyNewsOnceWeekIndex; + break; + case SurveyNewsPolicy.CheckOnceMonth: + _surveyNewsCheckCombo.SelectedIndex = SurveyNewsOnceMonthIndex; + break; + } + } + } + + internal void SyncControlWithPageSettings(NodejsGeneralOptionsPage page) { + SurveyNewsCheckCombo = page.SurveyNewsCheck; + _waitOnAbnormalExit.Checked = page.WaitOnAbnormalExit; + _waitOnNormalExit.Checked = page.WaitOnNormalExit; + _editAndContinue.Checked = page.EditAndContinue; + _checkForLongPaths.Checked = page.CheckForLongPaths; + _showBrowserAndNodeLabels.Checked = page.ShowBrowserAndNodeLabels; + + // Disable the show "browser" and "node" labels option when the user is in ES6 IntelliSense Preview mode + _showBrowserAndNodeLabels.Enabled = NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevel != AnalysisLevel.Preview; + } + + internal void SyncPageWithControlSettings(NodejsGeneralOptionsPage page) { + page.SurveyNewsCheck = SurveyNewsCheckCombo; + page.WaitOnAbnormalExit = _waitOnAbnormalExit.Checked; + page.WaitOnNormalExit = _waitOnNormalExit.Checked; + page.EditAndContinue = _editAndContinue.Checked; + page.CheckForLongPaths = _checkForLongPaths.Checked; + page.ShowBrowserAndNodeLabels = _showBrowserAndNodeLabels.Checked; + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs index d3e35fb73..e361a6f4d 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsGeneralOptionsPage.cs @@ -1,188 +1,188 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Runtime.InteropServices; -using System.Windows.Forms; - -namespace Microsoft.NodejsTools.Options { - [ComVisible(true)] - public class NodejsGeneralOptionsPage : NodejsDialogPage { - private const string DefaultSurveyNewsFeedUrl = "http://go.microsoft.com/fwlink/?LinkId=328027"; - private const string DefaultSurveyNewsIndexUrl = "http://go.microsoft.com/fwlink/?LinkId=328029"; - private const string SurveyNewsCheckSetting = "SurveyNewsCheck"; - private const string SurveyNewsLastCheckSetting = "SurveyNewsLastCheck"; - private const string SurveyNewsFeedUrlSetting = "SurveyNewsFeedUrl"; - private const string SurveyNewsIndexUrlSetting = "SurveyNewsIndexUrl"; - private const string WaitOnAbnormalExitSetting = "WaitOnAbnormalExit"; - private const string WaitOnNormalExitSetting = "WaitOnNormalExit"; - private const string EditAndContinueSetting = "EditAndContinue"; - private const string CheckForLongPathsSetting = "CheckForLongPaths"; - private const string ShowBrowserAndNodeLabelsSetting = "ShowBrowserAndNodeLabels"; - private bool _showBrowserAndNodeLabels; - private SurveyNewsPolicy _surveyNewsCheck; - private string _surveyNewsFeedUrl; - private string _surveyNewsIndexUrl; - private DateTime _surveyNewsLastCheck; - private NodejsGeneralOptionsControl _window; - - public NodejsGeneralOptionsPage() - : base("General") { - } - - // replace the default UI of the dialog page w/ our own UI. - protected override IWin32Window Window { - get { - if (_window == null) { - _window = new NodejsGeneralOptionsControl(); - LoadSettingsFromStorage(); - } - return _window; - } - } - - /// - /// Indicates whether or not the Output window should be shown when - /// npm commands are being executed. - /// - public bool ShowOutputWindowWhenExecutingNpm { get; set; } - - /// - /// True if Node processes should pause for input before exiting - /// if they exit abnormally. - /// - public bool WaitOnAbnormalExit { get; set; } - - /// - /// True if Node processes should pause for input before exiting - /// if they exit normally. - /// - public bool WaitOnNormalExit { get; set; } - - /// - /// Indicates whether Edit and Continue feature should be enabled. - /// - public bool EditAndContinue { get; set; } - - /// - /// Indicates whether checks for long paths (exceeding MAX_PATH) are performed after installing packages. - /// - public bool CheckForLongPaths { get; set; } - - /// - /// Indicates whether labels should be appended to folders in Solution Explorer denoting browser and Node.js code. - /// - public bool ShowBrowserAndNodeLabels { - get { return _showBrowserAndNodeLabels; } - set { - var oldSetting = _showBrowserAndNodeLabels; - _showBrowserAndNodeLabels = value; - if (oldSetting != _showBrowserAndNodeLabels) { - var changed = ShowBrowserAndNodeLabelsChanged; - if (changed != null) { - changed(this, EventArgs.Empty); - } - } - } - } - - public event EventHandler ShowBrowserAndNodeLabelsChanged; - - /// - /// The frequency at which to check for updated news. Default is once - /// per week. - /// - public SurveyNewsPolicy SurveyNewsCheck { - get { return _surveyNewsCheck; } - set { _surveyNewsCheck = value; } - } - - /// - /// The date/time when the last check for news occurred. - /// - public DateTime SurveyNewsLastCheck { - get { return _surveyNewsLastCheck; } - set { _surveyNewsLastCheck = value; } - } - - /// - /// The url of the news feed. - /// - public string SurveyNewsFeedUrl { - get { return _surveyNewsFeedUrl; } - set { _surveyNewsFeedUrl = value; } - } - - /// - /// The url of the news index page. - /// - public string SurveyNewsIndexUrl { - get { return _surveyNewsIndexUrl; } - set { _surveyNewsIndexUrl = value; } - } - - /// - /// Resets settings back to their defaults. This should be followed by - /// a call to to commit the new - /// values. - /// - public override void ResetSettings() { - _surveyNewsCheck = SurveyNewsPolicy.CheckOnceWeek; - _surveyNewsLastCheck = DateTime.MinValue; - _surveyNewsFeedUrl = DefaultSurveyNewsFeedUrl; - _surveyNewsIndexUrl = DefaultSurveyNewsIndexUrl; - WaitOnAbnormalExit = true; - WaitOnNormalExit = false; - EditAndContinue = true; - CheckForLongPaths = true; - _showBrowserAndNodeLabels = true; - } - - public override void LoadSettingsFromStorage() { - // Load settings from storage. - _surveyNewsCheck = LoadEnum(SurveyNewsCheckSetting) ?? SurveyNewsPolicy.CheckOnceWeek; - _surveyNewsLastCheck = LoadDateTime(SurveyNewsLastCheckSetting) ?? DateTime.MinValue; - _surveyNewsFeedUrl = LoadString(SurveyNewsFeedUrlSetting) ?? DefaultSurveyNewsFeedUrl; - _surveyNewsIndexUrl = LoadString(SurveyNewsIndexUrlSetting) ?? DefaultSurveyNewsIndexUrl; - WaitOnAbnormalExit = LoadBool(WaitOnAbnormalExitSetting) ?? true; - WaitOnNormalExit = LoadBool(WaitOnNormalExitSetting) ?? false; - EditAndContinue = LoadBool(EditAndContinueSetting) ?? true; - CheckForLongPaths = LoadBool(CheckForLongPathsSetting) ?? true; - _showBrowserAndNodeLabels = LoadBool(ShowBrowserAndNodeLabelsSetting) ?? true; - - // Synchronize UI with backing properties. - if (_window != null) { - _window.SyncControlWithPageSettings(this); - } - } - - public override void SaveSettingsToStorage() { - // Synchronize backing properties with UI. - if (_window != null) { - _window.SyncPageWithControlSettings(this); - } - - // Save settings. - SaveEnum(SurveyNewsCheckSetting, _surveyNewsCheck); - SaveDateTime(SurveyNewsLastCheckSetting, _surveyNewsLastCheck); - SaveBool(WaitOnNormalExitSetting, WaitOnNormalExit); - SaveBool(WaitOnAbnormalExitSetting, WaitOnAbnormalExit); - SaveBool(EditAndContinueSetting, EditAndContinue); - SaveBool(CheckForLongPathsSetting, CheckForLongPaths); - SaveBool(ShowBrowserAndNodeLabelsSetting, ShowBrowserAndNodeLabels); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Runtime.InteropServices; +using System.Windows.Forms; + +namespace Microsoft.NodejsTools.Options { + [ComVisible(true)] + public class NodejsGeneralOptionsPage : NodejsDialogPage { + private const string DefaultSurveyNewsFeedUrl = "http://go.microsoft.com/fwlink/?LinkId=328027"; + private const string DefaultSurveyNewsIndexUrl = "http://go.microsoft.com/fwlink/?LinkId=328029"; + private const string SurveyNewsCheckSetting = "SurveyNewsCheck"; + private const string SurveyNewsLastCheckSetting = "SurveyNewsLastCheck"; + private const string SurveyNewsFeedUrlSetting = "SurveyNewsFeedUrl"; + private const string SurveyNewsIndexUrlSetting = "SurveyNewsIndexUrl"; + private const string WaitOnAbnormalExitSetting = "WaitOnAbnormalExit"; + private const string WaitOnNormalExitSetting = "WaitOnNormalExit"; + private const string EditAndContinueSetting = "EditAndContinue"; + private const string CheckForLongPathsSetting = "CheckForLongPaths"; + private const string ShowBrowserAndNodeLabelsSetting = "ShowBrowserAndNodeLabels"; + private bool _showBrowserAndNodeLabels; + private SurveyNewsPolicy _surveyNewsCheck; + private string _surveyNewsFeedUrl; + private string _surveyNewsIndexUrl; + private DateTime _surveyNewsLastCheck; + private NodejsGeneralOptionsControl _window; + + public NodejsGeneralOptionsPage() + : base("General") { + } + + // replace the default UI of the dialog page w/ our own UI. + protected override IWin32Window Window { + get { + if (_window == null) { + _window = new NodejsGeneralOptionsControl(); + LoadSettingsFromStorage(); + } + return _window; + } + } + + /// + /// Indicates whether or not the Output window should be shown when + /// npm commands are being executed. + /// + public bool ShowOutputWindowWhenExecutingNpm { get; set; } + + /// + /// True if Node processes should pause for input before exiting + /// if they exit abnormally. + /// + public bool WaitOnAbnormalExit { get; set; } + + /// + /// True if Node processes should pause for input before exiting + /// if they exit normally. + /// + public bool WaitOnNormalExit { get; set; } + + /// + /// Indicates whether Edit and Continue feature should be enabled. + /// + public bool EditAndContinue { get; set; } + + /// + /// Indicates whether checks for long paths (exceeding MAX_PATH) are performed after installing packages. + /// + public bool CheckForLongPaths { get; set; } + + /// + /// Indicates whether labels should be appended to folders in Solution Explorer denoting browser and Node.js code. + /// + public bool ShowBrowserAndNodeLabels { + get { return _showBrowserAndNodeLabels; } + set { + var oldSetting = _showBrowserAndNodeLabels; + _showBrowserAndNodeLabels = value; + if (oldSetting != _showBrowserAndNodeLabels) { + var changed = ShowBrowserAndNodeLabelsChanged; + if (changed != null) { + changed(this, EventArgs.Empty); + } + } + } + } + + public event EventHandler ShowBrowserAndNodeLabelsChanged; + + /// + /// The frequency at which to check for updated news. Default is once + /// per week. + /// + public SurveyNewsPolicy SurveyNewsCheck { + get { return _surveyNewsCheck; } + set { _surveyNewsCheck = value; } + } + + /// + /// The date/time when the last check for news occurred. + /// + public DateTime SurveyNewsLastCheck { + get { return _surveyNewsLastCheck; } + set { _surveyNewsLastCheck = value; } + } + + /// + /// The url of the news feed. + /// + public string SurveyNewsFeedUrl { + get { return _surveyNewsFeedUrl; } + set { _surveyNewsFeedUrl = value; } + } + + /// + /// The url of the news index page. + /// + public string SurveyNewsIndexUrl { + get { return _surveyNewsIndexUrl; } + set { _surveyNewsIndexUrl = value; } + } + + /// + /// Resets settings back to their defaults. This should be followed by + /// a call to to commit the new + /// values. + /// + public override void ResetSettings() { + _surveyNewsCheck = SurveyNewsPolicy.CheckOnceWeek; + _surveyNewsLastCheck = DateTime.MinValue; + _surveyNewsFeedUrl = DefaultSurveyNewsFeedUrl; + _surveyNewsIndexUrl = DefaultSurveyNewsIndexUrl; + WaitOnAbnormalExit = true; + WaitOnNormalExit = false; + EditAndContinue = true; + CheckForLongPaths = true; + _showBrowserAndNodeLabels = true; + } + + public override void LoadSettingsFromStorage() { + // Load settings from storage. + _surveyNewsCheck = LoadEnum(SurveyNewsCheckSetting) ?? SurveyNewsPolicy.CheckOnceWeek; + _surveyNewsLastCheck = LoadDateTime(SurveyNewsLastCheckSetting) ?? DateTime.MinValue; + _surveyNewsFeedUrl = LoadString(SurveyNewsFeedUrlSetting) ?? DefaultSurveyNewsFeedUrl; + _surveyNewsIndexUrl = LoadString(SurveyNewsIndexUrlSetting) ?? DefaultSurveyNewsIndexUrl; + WaitOnAbnormalExit = LoadBool(WaitOnAbnormalExitSetting) ?? true; + WaitOnNormalExit = LoadBool(WaitOnNormalExitSetting) ?? false; + EditAndContinue = LoadBool(EditAndContinueSetting) ?? true; + CheckForLongPaths = LoadBool(CheckForLongPathsSetting) ?? true; + _showBrowserAndNodeLabels = LoadBool(ShowBrowserAndNodeLabelsSetting) ?? true; + + // Synchronize UI with backing properties. + if (_window != null) { + _window.SyncControlWithPageSettings(this); + } + } + + public override void SaveSettingsToStorage() { + // Synchronize backing properties with UI. + if (_window != null) { + _window.SyncPageWithControlSettings(this); + } + + // Save settings. + SaveEnum(SurveyNewsCheckSetting, _surveyNewsCheck); + SaveDateTime(SurveyNewsLastCheckSetting, _surveyNewsLastCheck); + SaveBool(WaitOnNormalExitSetting, WaitOnNormalExit); + SaveBool(WaitOnAbnormalExitSetting, WaitOnAbnormalExit); + SaveBool(EditAndContinueSetting, EditAndContinue); + SaveBool(CheckForLongPathsSetting, CheckForLongPaths); + SaveBool(ShowBrowserAndNodeLabelsSetting, ShowBrowserAndNodeLabels); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsControl.Designer.cs b/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsControl.Designer.cs index bc8fb664e..9357ee1bc 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsControl.Designer.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsControl.Designer.cs @@ -1,245 +1,245 @@ -namespace Microsoft.NodejsTools.Options { - partial class NodejsIntellisenseOptionsControl { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) { - if (disposing && (components != null)) { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Component Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() { - this.components = new System.ComponentModel.Container(); - System.Windows.Forms.ToolTip toolTip; - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NodejsIntellisenseOptionsControl)); - System.Windows.Forms.GroupBox intellisenseLevelGroupBox; - System.Windows.Forms.GroupBox saveToDiskGroupBox; - this._saveToDiskDisabledRadioButton = new System.Windows.Forms.RadioButton(); - this._saveToDiskEnabledRadioButton = new System.Windows.Forms.RadioButton(); - this._fullIntelliSenseRadioButton = new System.Windows.Forms.RadioButton(); - this._noIntelliSenseRadioButton = new System.Windows.Forms.RadioButton(); - this._mediumIntelliSenseRadioButton = new System.Windows.Forms.RadioButton(); - this._previewIntelliSenseRadioButton = new System.Windows.Forms.RadioButton(); - this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); - this._analysisLogMax = new System.Windows.Forms.ComboBox(); - this._analysisLogMaxLabel = new System.Windows.Forms.Label(); - this._analysisPreviewFeedbackLinkLabel = new System.Windows.Forms.LinkLabel(); - this.tableLayoutPanel4 = new System.Windows.Forms.TableLayoutPanel(); - this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); - this._selectionInCompletionListGroupBox = new System.Windows.Forms.GroupBox(); - this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); - this._onlyTabOrEnterToCommit = new System.Windows.Forms.CheckBox(); - this._showCompletionListAfterCharacterTyped = new System.Windows.Forms.CheckBox(); - toolTip = new System.Windows.Forms.ToolTip(this.components); - intellisenseLevelGroupBox = new System.Windows.Forms.GroupBox(); - saveToDiskGroupBox = new System.Windows.Forms.GroupBox(); - intellisenseLevelGroupBox.SuspendLayout(); - this.tableLayoutPanel2.SuspendLayout(); - saveToDiskGroupBox.SuspendLayout(); - this.tableLayoutPanel4.SuspendLayout(); - this.tableLayoutPanel1.SuspendLayout(); - this._selectionInCompletionListGroupBox.SuspendLayout(); - this.tableLayoutPanel3.SuspendLayout(); - this.SuspendLayout(); - // - // _saveToDiskDisabledRadioButton - // - resources.ApplyResources(this._saveToDiskDisabledRadioButton, "_saveToDiskDisabledRadioButton"); - this._saveToDiskDisabledRadioButton.Name = "_saveToDiskDisabledRadioButton"; - this._saveToDiskDisabledRadioButton.TabStop = true; - toolTip.SetToolTip(this._saveToDiskDisabledRadioButton, resources.GetString("_saveToDiskDisabledRadioButton.ToolTip")); - this._saveToDiskDisabledRadioButton.UseVisualStyleBackColor = true; - // - // _saveToDiskEnabledRadioButton - // - resources.ApplyResources(this._saveToDiskEnabledRadioButton, "_saveToDiskEnabledRadioButton"); - this._saveToDiskEnabledRadioButton.Name = "_saveToDiskEnabledRadioButton"; - this._saveToDiskEnabledRadioButton.TabStop = true; - toolTip.SetToolTip(this._saveToDiskEnabledRadioButton, resources.GetString("_saveToDiskEnabledRadioButton.ToolTip")); - this._saveToDiskEnabledRadioButton.UseVisualStyleBackColor = true; - // - // _fullIntelliSenseRadioButton - // - resources.ApplyResources(this._fullIntelliSenseRadioButton, "_fullIntelliSenseRadioButton"); - this._fullIntelliSenseRadioButton.Name = "_fullIntelliSenseRadioButton"; - this._fullIntelliSenseRadioButton.TabStop = true; - toolTip.SetToolTip(this._fullIntelliSenseRadioButton, resources.GetString("_fullIntelliSenseRadioButton.ToolTip")); - this._fullIntelliSenseRadioButton.UseVisualStyleBackColor = true; - // - // _noIntelliSenseRadioButton - // - resources.ApplyResources(this._noIntelliSenseRadioButton, "_noIntelliSenseRadioButton"); - this._noIntelliSenseRadioButton.Name = "_noIntelliSenseRadioButton"; - this._noIntelliSenseRadioButton.TabStop = true; - toolTip.SetToolTip(this._noIntelliSenseRadioButton, resources.GetString("_noIntelliSenseRadioButton.ToolTip")); - this._noIntelliSenseRadioButton.UseVisualStyleBackColor = true; - // - // _mediumIntelliSenseRadioButton - // - resources.ApplyResources(this._mediumIntelliSenseRadioButton, "_mediumIntelliSenseRadioButton"); - this._mediumIntelliSenseRadioButton.Name = "_mediumIntelliSenseRadioButton"; - this._mediumIntelliSenseRadioButton.TabStop = true; - toolTip.SetToolTip(this._mediumIntelliSenseRadioButton, resources.GetString("_mediumIntelliSenseRadioButton.ToolTip")); - this._mediumIntelliSenseRadioButton.UseVisualStyleBackColor = true; - // - // _previewIntelliSenseRadioButton - // - resources.ApplyResources(this._previewIntelliSenseRadioButton, "_previewIntelliSenseRadioButton"); - this._previewIntelliSenseRadioButton.Name = "_previewIntelliSenseRadioButton"; - this._previewIntelliSenseRadioButton.TabStop = true; - toolTip.SetToolTip(this._previewIntelliSenseRadioButton, resources.GetString("_previewIntelliSenseRadioButton.ToolTip")); - this._previewIntelliSenseRadioButton.UseVisualStyleBackColor = true; - // - // intellisenseLevelGroupBox - // - resources.ApplyResources(intellisenseLevelGroupBox, "intellisenseLevelGroupBox"); - intellisenseLevelGroupBox.Controls.Add(this.tableLayoutPanel2); - intellisenseLevelGroupBox.Name = "intellisenseLevelGroupBox"; - intellisenseLevelGroupBox.TabStop = false; - // - // tableLayoutPanel2 - // - resources.ApplyResources(this.tableLayoutPanel2, "tableLayoutPanel2"); - this.tableLayoutPanel2.Controls.Add(this._previewIntelliSenseRadioButton, 0, 4); - this.tableLayoutPanel2.Controls.Add(this._mediumIntelliSenseRadioButton, 0, 2); - this.tableLayoutPanel2.Controls.Add(this._analysisLogMax, 1, 5); - this.tableLayoutPanel2.Controls.Add(this._noIntelliSenseRadioButton, 0, 3); - this.tableLayoutPanel2.Controls.Add(this._analysisLogMaxLabel, 0, 5); - this.tableLayoutPanel2.Controls.Add(this._fullIntelliSenseRadioButton, 0, 0); - this.tableLayoutPanel2.Controls.Add(this._analysisPreviewFeedbackLinkLabel, 1, 4); - this.tableLayoutPanel2.Name = "tableLayoutPanel2"; - // - // _analysisLogMax - // - resources.ApplyResources(this._analysisLogMax, "_analysisLogMax"); - this._analysisLogMax.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; - this._analysisLogMax.FormattingEnabled = true; - this._analysisLogMax.Items.AddRange(new object[] { - resources.GetString("_analysisLogMax.Items"), - resources.GetString("_analysisLogMax.Items1"), - resources.GetString("_analysisLogMax.Items2"), - resources.GetString("_analysisLogMax.Items3"), - resources.GetString("_analysisLogMax.Items4"), - resources.GetString("_analysisLogMax.Items5"), - resources.GetString("_analysisLogMax.Items6")}); - this._analysisLogMax.Name = "_analysisLogMax"; - // - // _analysisLogMaxLabel - // - resources.ApplyResources(this._analysisLogMaxLabel, "_analysisLogMaxLabel"); - this._analysisLogMaxLabel.Name = "_analysisLogMaxLabel"; - // - // _analysisPreviewFeedbackLinkLabel - // - resources.ApplyResources(this._analysisPreviewFeedbackLinkLabel, "_analysisPreviewFeedbackLinkLabel"); - this._analysisPreviewFeedbackLinkLabel.Name = "_analysisPreviewFeedbackLinkLabel"; - this._analysisPreviewFeedbackLinkLabel.TabStop = true; - this._analysisPreviewFeedbackLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this._analysisPreviewFeedbackLinkLabel_LinkClicked); - // - // saveToDiskGroupBox - // - resources.ApplyResources(saveToDiskGroupBox, "saveToDiskGroupBox"); - saveToDiskGroupBox.Controls.Add(this.tableLayoutPanel4); - saveToDiskGroupBox.Name = "saveToDiskGroupBox"; - saveToDiskGroupBox.TabStop = false; - // - // tableLayoutPanel4 - // - resources.ApplyResources(this.tableLayoutPanel4, "tableLayoutPanel4"); - this.tableLayoutPanel4.Controls.Add(this._saveToDiskDisabledRadioButton, 0, 1); - this.tableLayoutPanel4.Controls.Add(this._saveToDiskEnabledRadioButton, 0, 0); - this.tableLayoutPanel4.Name = "tableLayoutPanel4"; - // - // tableLayoutPanel1 - // - resources.ApplyResources(this.tableLayoutPanel1, "tableLayoutPanel1"); - this.tableLayoutPanel1.Controls.Add(this._selectionInCompletionListGroupBox, 0, 2); - this.tableLayoutPanel1.Controls.Add(saveToDiskGroupBox, 0, 1); - this.tableLayoutPanel1.Controls.Add(intellisenseLevelGroupBox, 0, 0); - this.tableLayoutPanel1.Name = "tableLayoutPanel1"; - // - // _selectionInCompletionListGroupBox - // - resources.ApplyResources(this._selectionInCompletionListGroupBox, "_selectionInCompletionListGroupBox"); - this._selectionInCompletionListGroupBox.Controls.Add(this.tableLayoutPanel3); - this._selectionInCompletionListGroupBox.Name = "_selectionInCompletionListGroupBox"; - this._selectionInCompletionListGroupBox.TabStop = false; - // - // tableLayoutPanel3 - // - resources.ApplyResources(this.tableLayoutPanel3, "tableLayoutPanel3"); - this.tableLayoutPanel3.Controls.Add(this._onlyTabOrEnterToCommit, 0, 0); - this.tableLayoutPanel3.Controls.Add(this._showCompletionListAfterCharacterTyped, 0, 1); - this.tableLayoutPanel3.Name = "tableLayoutPanel3"; - // - // _onlyTabOrEnterToCommit - // - resources.ApplyResources(this._onlyTabOrEnterToCommit, "_onlyTabOrEnterToCommit"); - this._onlyTabOrEnterToCommit.Name = "_onlyTabOrEnterToCommit"; - this._onlyTabOrEnterToCommit.UseVisualStyleBackColor = true; - // - // _showCompletionListAfterCharacterTyped - // - resources.ApplyResources(this._showCompletionListAfterCharacterTyped, "_showCompletionListAfterCharacterTyped"); - this._showCompletionListAfterCharacterTyped.Name = "_showCompletionListAfterCharacterTyped"; - this._showCompletionListAfterCharacterTyped.UseVisualStyleBackColor = true; - // - // NodejsIntellisenseOptionsControl - // - resources.ApplyResources(this, "$this"); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.Controls.Add(this.tableLayoutPanel1); - this.Name = "NodejsIntellisenseOptionsControl"; - intellisenseLevelGroupBox.ResumeLayout(false); - intellisenseLevelGroupBox.PerformLayout(); - this.tableLayoutPanel2.ResumeLayout(false); - this.tableLayoutPanel2.PerformLayout(); - saveToDiskGroupBox.ResumeLayout(false); - saveToDiskGroupBox.PerformLayout(); - this.tableLayoutPanel4.ResumeLayout(false); - this.tableLayoutPanel4.PerformLayout(); - this.tableLayoutPanel1.ResumeLayout(false); - this.tableLayoutPanel1.PerformLayout(); - this._selectionInCompletionListGroupBox.ResumeLayout(false); - this._selectionInCompletionListGroupBox.PerformLayout(); - this.tableLayoutPanel3.ResumeLayout(false); - this.tableLayoutPanel3.PerformLayout(); - this.ResumeLayout(false); - this.PerformLayout(); - - } - - #endregion - - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; - private System.Windows.Forms.GroupBox _selectionInCompletionListGroupBox; - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel4; - private System.Windows.Forms.RadioButton _saveToDiskDisabledRadioButton; - private System.Windows.Forms.RadioButton _saveToDiskEnabledRadioButton; - private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; - private System.Windows.Forms.RadioButton _previewIntelliSenseRadioButton; - private System.Windows.Forms.RadioButton _mediumIntelliSenseRadioButton; - private System.Windows.Forms.ComboBox _analysisLogMax; - private System.Windows.Forms.RadioButton _noIntelliSenseRadioButton; - private System.Windows.Forms.Label _analysisLogMaxLabel; - private System.Windows.Forms.RadioButton _fullIntelliSenseRadioButton; - private System.Windows.Forms.LinkLabel _analysisPreviewFeedbackLinkLabel; - private System.Windows.Forms.CheckBox _onlyTabOrEnterToCommit; - private System.Windows.Forms.CheckBox _showCompletionListAfterCharacterTyped; - } -} +namespace Microsoft.NodejsTools.Options { + partial class NodejsIntellisenseOptionsControl { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.components = new System.ComponentModel.Container(); + System.Windows.Forms.ToolTip toolTip; + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(NodejsIntellisenseOptionsControl)); + System.Windows.Forms.GroupBox intellisenseLevelGroupBox; + System.Windows.Forms.GroupBox saveToDiskGroupBox; + this._saveToDiskDisabledRadioButton = new System.Windows.Forms.RadioButton(); + this._saveToDiskEnabledRadioButton = new System.Windows.Forms.RadioButton(); + this._fullIntelliSenseRadioButton = new System.Windows.Forms.RadioButton(); + this._noIntelliSenseRadioButton = new System.Windows.Forms.RadioButton(); + this._mediumIntelliSenseRadioButton = new System.Windows.Forms.RadioButton(); + this._previewIntelliSenseRadioButton = new System.Windows.Forms.RadioButton(); + this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel(); + this._analysisLogMax = new System.Windows.Forms.ComboBox(); + this._analysisLogMaxLabel = new System.Windows.Forms.Label(); + this._analysisPreviewFeedbackLinkLabel = new System.Windows.Forms.LinkLabel(); + this.tableLayoutPanel4 = new System.Windows.Forms.TableLayoutPanel(); + this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel(); + this._selectionInCompletionListGroupBox = new System.Windows.Forms.GroupBox(); + this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel(); + this._onlyTabOrEnterToCommit = new System.Windows.Forms.CheckBox(); + this._showCompletionListAfterCharacterTyped = new System.Windows.Forms.CheckBox(); + toolTip = new System.Windows.Forms.ToolTip(this.components); + intellisenseLevelGroupBox = new System.Windows.Forms.GroupBox(); + saveToDiskGroupBox = new System.Windows.Forms.GroupBox(); + intellisenseLevelGroupBox.SuspendLayout(); + this.tableLayoutPanel2.SuspendLayout(); + saveToDiskGroupBox.SuspendLayout(); + this.tableLayoutPanel4.SuspendLayout(); + this.tableLayoutPanel1.SuspendLayout(); + this._selectionInCompletionListGroupBox.SuspendLayout(); + this.tableLayoutPanel3.SuspendLayout(); + this.SuspendLayout(); + // + // _saveToDiskDisabledRadioButton + // + resources.ApplyResources(this._saveToDiskDisabledRadioButton, "_saveToDiskDisabledRadioButton"); + this._saveToDiskDisabledRadioButton.Name = "_saveToDiskDisabledRadioButton"; + this._saveToDiskDisabledRadioButton.TabStop = true; + toolTip.SetToolTip(this._saveToDiskDisabledRadioButton, resources.GetString("_saveToDiskDisabledRadioButton.ToolTip")); + this._saveToDiskDisabledRadioButton.UseVisualStyleBackColor = true; + // + // _saveToDiskEnabledRadioButton + // + resources.ApplyResources(this._saveToDiskEnabledRadioButton, "_saveToDiskEnabledRadioButton"); + this._saveToDiskEnabledRadioButton.Name = "_saveToDiskEnabledRadioButton"; + this._saveToDiskEnabledRadioButton.TabStop = true; + toolTip.SetToolTip(this._saveToDiskEnabledRadioButton, resources.GetString("_saveToDiskEnabledRadioButton.ToolTip")); + this._saveToDiskEnabledRadioButton.UseVisualStyleBackColor = true; + // + // _fullIntelliSenseRadioButton + // + resources.ApplyResources(this._fullIntelliSenseRadioButton, "_fullIntelliSenseRadioButton"); + this._fullIntelliSenseRadioButton.Name = "_fullIntelliSenseRadioButton"; + this._fullIntelliSenseRadioButton.TabStop = true; + toolTip.SetToolTip(this._fullIntelliSenseRadioButton, resources.GetString("_fullIntelliSenseRadioButton.ToolTip")); + this._fullIntelliSenseRadioButton.UseVisualStyleBackColor = true; + // + // _noIntelliSenseRadioButton + // + resources.ApplyResources(this._noIntelliSenseRadioButton, "_noIntelliSenseRadioButton"); + this._noIntelliSenseRadioButton.Name = "_noIntelliSenseRadioButton"; + this._noIntelliSenseRadioButton.TabStop = true; + toolTip.SetToolTip(this._noIntelliSenseRadioButton, resources.GetString("_noIntelliSenseRadioButton.ToolTip")); + this._noIntelliSenseRadioButton.UseVisualStyleBackColor = true; + // + // _mediumIntelliSenseRadioButton + // + resources.ApplyResources(this._mediumIntelliSenseRadioButton, "_mediumIntelliSenseRadioButton"); + this._mediumIntelliSenseRadioButton.Name = "_mediumIntelliSenseRadioButton"; + this._mediumIntelliSenseRadioButton.TabStop = true; + toolTip.SetToolTip(this._mediumIntelliSenseRadioButton, resources.GetString("_mediumIntelliSenseRadioButton.ToolTip")); + this._mediumIntelliSenseRadioButton.UseVisualStyleBackColor = true; + // + // _previewIntelliSenseRadioButton + // + resources.ApplyResources(this._previewIntelliSenseRadioButton, "_previewIntelliSenseRadioButton"); + this._previewIntelliSenseRadioButton.Name = "_previewIntelliSenseRadioButton"; + this._previewIntelliSenseRadioButton.TabStop = true; + toolTip.SetToolTip(this._previewIntelliSenseRadioButton, resources.GetString("_previewIntelliSenseRadioButton.ToolTip")); + this._previewIntelliSenseRadioButton.UseVisualStyleBackColor = true; + // + // intellisenseLevelGroupBox + // + resources.ApplyResources(intellisenseLevelGroupBox, "intellisenseLevelGroupBox"); + intellisenseLevelGroupBox.Controls.Add(this.tableLayoutPanel2); + intellisenseLevelGroupBox.Name = "intellisenseLevelGroupBox"; + intellisenseLevelGroupBox.TabStop = false; + // + // tableLayoutPanel2 + // + resources.ApplyResources(this.tableLayoutPanel2, "tableLayoutPanel2"); + this.tableLayoutPanel2.Controls.Add(this._previewIntelliSenseRadioButton, 0, 4); + this.tableLayoutPanel2.Controls.Add(this._mediumIntelliSenseRadioButton, 0, 2); + this.tableLayoutPanel2.Controls.Add(this._analysisLogMax, 1, 5); + this.tableLayoutPanel2.Controls.Add(this._noIntelliSenseRadioButton, 0, 3); + this.tableLayoutPanel2.Controls.Add(this._analysisLogMaxLabel, 0, 5); + this.tableLayoutPanel2.Controls.Add(this._fullIntelliSenseRadioButton, 0, 0); + this.tableLayoutPanel2.Controls.Add(this._analysisPreviewFeedbackLinkLabel, 1, 4); + this.tableLayoutPanel2.Name = "tableLayoutPanel2"; + // + // _analysisLogMax + // + resources.ApplyResources(this._analysisLogMax, "_analysisLogMax"); + this._analysisLogMax.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this._analysisLogMax.FormattingEnabled = true; + this._analysisLogMax.Items.AddRange(new object[] { + resources.GetString("_analysisLogMax.Items"), + resources.GetString("_analysisLogMax.Items1"), + resources.GetString("_analysisLogMax.Items2"), + resources.GetString("_analysisLogMax.Items3"), + resources.GetString("_analysisLogMax.Items4"), + resources.GetString("_analysisLogMax.Items5"), + resources.GetString("_analysisLogMax.Items6")}); + this._analysisLogMax.Name = "_analysisLogMax"; + // + // _analysisLogMaxLabel + // + resources.ApplyResources(this._analysisLogMaxLabel, "_analysisLogMaxLabel"); + this._analysisLogMaxLabel.Name = "_analysisLogMaxLabel"; + // + // _analysisPreviewFeedbackLinkLabel + // + resources.ApplyResources(this._analysisPreviewFeedbackLinkLabel, "_analysisPreviewFeedbackLinkLabel"); + this._analysisPreviewFeedbackLinkLabel.Name = "_analysisPreviewFeedbackLinkLabel"; + this._analysisPreviewFeedbackLinkLabel.TabStop = true; + this._analysisPreviewFeedbackLinkLabel.LinkClicked += new System.Windows.Forms.LinkLabelLinkClickedEventHandler(this._analysisPreviewFeedbackLinkLabel_LinkClicked); + // + // saveToDiskGroupBox + // + resources.ApplyResources(saveToDiskGroupBox, "saveToDiskGroupBox"); + saveToDiskGroupBox.Controls.Add(this.tableLayoutPanel4); + saveToDiskGroupBox.Name = "saveToDiskGroupBox"; + saveToDiskGroupBox.TabStop = false; + // + // tableLayoutPanel4 + // + resources.ApplyResources(this.tableLayoutPanel4, "tableLayoutPanel4"); + this.tableLayoutPanel4.Controls.Add(this._saveToDiskDisabledRadioButton, 0, 1); + this.tableLayoutPanel4.Controls.Add(this._saveToDiskEnabledRadioButton, 0, 0); + this.tableLayoutPanel4.Name = "tableLayoutPanel4"; + // + // tableLayoutPanel1 + // + resources.ApplyResources(this.tableLayoutPanel1, "tableLayoutPanel1"); + this.tableLayoutPanel1.Controls.Add(this._selectionInCompletionListGroupBox, 0, 2); + this.tableLayoutPanel1.Controls.Add(saveToDiskGroupBox, 0, 1); + this.tableLayoutPanel1.Controls.Add(intellisenseLevelGroupBox, 0, 0); + this.tableLayoutPanel1.Name = "tableLayoutPanel1"; + // + // _selectionInCompletionListGroupBox + // + resources.ApplyResources(this._selectionInCompletionListGroupBox, "_selectionInCompletionListGroupBox"); + this._selectionInCompletionListGroupBox.Controls.Add(this.tableLayoutPanel3); + this._selectionInCompletionListGroupBox.Name = "_selectionInCompletionListGroupBox"; + this._selectionInCompletionListGroupBox.TabStop = false; + // + // tableLayoutPanel3 + // + resources.ApplyResources(this.tableLayoutPanel3, "tableLayoutPanel3"); + this.tableLayoutPanel3.Controls.Add(this._onlyTabOrEnterToCommit, 0, 0); + this.tableLayoutPanel3.Controls.Add(this._showCompletionListAfterCharacterTyped, 0, 1); + this.tableLayoutPanel3.Name = "tableLayoutPanel3"; + // + // _onlyTabOrEnterToCommit + // + resources.ApplyResources(this._onlyTabOrEnterToCommit, "_onlyTabOrEnterToCommit"); + this._onlyTabOrEnterToCommit.Name = "_onlyTabOrEnterToCommit"; + this._onlyTabOrEnterToCommit.UseVisualStyleBackColor = true; + // + // _showCompletionListAfterCharacterTyped + // + resources.ApplyResources(this._showCompletionListAfterCharacterTyped, "_showCompletionListAfterCharacterTyped"); + this._showCompletionListAfterCharacterTyped.Name = "_showCompletionListAfterCharacterTyped"; + this._showCompletionListAfterCharacterTyped.UseVisualStyleBackColor = true; + // + // NodejsIntellisenseOptionsControl + // + resources.ApplyResources(this, "$this"); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.tableLayoutPanel1); + this.Name = "NodejsIntellisenseOptionsControl"; + intellisenseLevelGroupBox.ResumeLayout(false); + intellisenseLevelGroupBox.PerformLayout(); + this.tableLayoutPanel2.ResumeLayout(false); + this.tableLayoutPanel2.PerformLayout(); + saveToDiskGroupBox.ResumeLayout(false); + saveToDiskGroupBox.PerformLayout(); + this.tableLayoutPanel4.ResumeLayout(false); + this.tableLayoutPanel4.PerformLayout(); + this.tableLayoutPanel1.ResumeLayout(false); + this.tableLayoutPanel1.PerformLayout(); + this._selectionInCompletionListGroupBox.ResumeLayout(false); + this._selectionInCompletionListGroupBox.PerformLayout(); + this.tableLayoutPanel3.ResumeLayout(false); + this.tableLayoutPanel3.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1; + private System.Windows.Forms.GroupBox _selectionInCompletionListGroupBox; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel4; + private System.Windows.Forms.RadioButton _saveToDiskDisabledRadioButton; + private System.Windows.Forms.RadioButton _saveToDiskEnabledRadioButton; + private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2; + private System.Windows.Forms.RadioButton _previewIntelliSenseRadioButton; + private System.Windows.Forms.RadioButton _mediumIntelliSenseRadioButton; + private System.Windows.Forms.ComboBox _analysisLogMax; + private System.Windows.Forms.RadioButton _noIntelliSenseRadioButton; + private System.Windows.Forms.Label _analysisLogMaxLabel; + private System.Windows.Forms.RadioButton _fullIntelliSenseRadioButton; + private System.Windows.Forms.LinkLabel _analysisPreviewFeedbackLinkLabel; + private System.Windows.Forms.CheckBox _onlyTabOrEnterToCommit; + private System.Windows.Forms.CheckBox _showCompletionListAfterCharacterTyped; + } +} diff --git a/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsControl.cs b/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsControl.cs index 4b50a5ac6..15c0ff244 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsControl.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsControl.cs @@ -22,7 +22,7 @@ namespace Microsoft.NodejsTools.Options { public partial class NodejsIntellisenseOptionsControl : UserControl { public NodejsIntellisenseOptionsControl() { - InitializeComponent(); + InitializeComponent(); _previewIntelliSenseRadioButton.Enabled = NodejsPackage.Instance.IntellisenseOptionsPage.EnableES6Preview; } @@ -40,9 +40,9 @@ internal bool SaveToDisk { } internal AnalysisLevel AnalysisLevel { - get { - if (_previewIntelliSenseRadioButton.Checked) { - return AnalysisLevel.Preview; + get { + if (_previewIntelliSenseRadioButton.Checked) { + return AnalysisLevel.Preview; } else if (_fullIntelliSenseRadioButton.Checked) { return AnalysisLevel.High; } else if (_mediumIntelliSenseRadioButton.Checked) { @@ -52,9 +52,9 @@ internal AnalysisLevel AnalysisLevel { } } set { - switch (value) { - case AnalysisLevel.Preview: - _previewIntelliSenseRadioButton.Checked = true; + switch (value) { + case AnalysisLevel.Preview: + _previewIntelliSenseRadioButton.Checked = true; break; case AnalysisLevel.High: _fullIntelliSenseRadioButton.Checked = true; @@ -102,42 +102,42 @@ internal int AnalysisLogMaximum { } } - internal bool OnlyTabOrEnterToCommit { - get { - return _onlyTabOrEnterToCommit.Checked; - } - set { - _onlyTabOrEnterToCommit.Checked = value; - } + internal bool OnlyTabOrEnterToCommit { + get { + return _onlyTabOrEnterToCommit.Checked; + } + set { + _onlyTabOrEnterToCommit.Checked = value; + } } - internal bool ShowCompletionListAfterCharacterTyped { - get { - return _showCompletionListAfterCharacterTyped.Checked; - } - set { - _showCompletionListAfterCharacterTyped.Checked = value; - } + internal bool ShowCompletionListAfterCharacterTyped { + get { + return _showCompletionListAfterCharacterTyped.Checked; + } + set { + _showCompletionListAfterCharacterTyped.Checked = value; + } } internal void SyncPageWithControlSettings(NodejsIntellisenseOptionsPage page) { page.AnalysisLevel = AnalysisLevel; page.AnalysisLogMax = AnalysisLogMaximum; - page.SaveToDisk = SaveToDisk; + page.SaveToDisk = SaveToDisk; page.OnlyTabOrEnterToCommit = OnlyTabOrEnterToCommit; page.ShowCompletionListAfterCharacterTyped = ShowCompletionListAfterCharacterTyped; } internal void SyncControlWithPageSettings(NodejsIntellisenseOptionsPage page) { AnalysisLevel = page.AnalysisLevel; - AnalysisLogMaximum = page.AnalysisLogMax; + AnalysisLogMaximum = page.AnalysisLogMax; SaveToDisk = page.SaveToDisk; OnlyTabOrEnterToCommit = page.OnlyTabOrEnterToCommit; ShowCompletionListAfterCharacterTyped = page.ShowCompletionListAfterCharacterTyped; - } - - private void _analysisPreviewFeedbackLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { - Process.Start("http://aka.ms/NtvsEs6Preview"); + } + + private void _analysisPreviewFeedbackLinkLabel_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) { + Process.Start("http://aka.ms/NtvsEs6Preview"); } } } diff --git a/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsPage.cs b/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsPage.cs index 31dab60bf..91e4c2b54 100644 --- a/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsPage.cs +++ b/Nodejs/Product/Nodejs/Options/NodejsIntellisenseOptionsPage.cs @@ -12,15 +12,15 @@ // implied. See the License for the specific language governing // permissions and limitations under the License. // -//*********************************************************// - -using System; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text.RegularExpressions; -using Microsoft.VisualStudio.Shell.Interop; +//*********************************************************// + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text.RegularExpressions; +using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudioTools; namespace Microsoft.NodejsTools.Options { @@ -30,20 +30,20 @@ public class NodejsIntellisenseOptionsPage : NodejsDialogPage { private AnalysisLevel _level; private int _analysisLogMax; private bool _saveToDisk; - private bool _onlyTabOrEnterToCommit; - private bool _showCompletionListAfterCharacterTyped; - private string _toolsVersion; - private readonly bool _enableES6Preview; + private bool _onlyTabOrEnterToCommit; + private bool _showCompletionListAfterCharacterTyped; + private string _toolsVersion; + private readonly bool _enableES6Preview; private readonly Version _typeScriptMinVersionForES6Preview = new Version("1.6"); public NodejsIntellisenseOptionsPage() - : base("IntelliSense") { - Version version; - var versionString = GetTypeScriptToolsVersion(); - if (!string.IsNullOrEmpty(versionString) && - Version.TryParse(versionString, out version) && - version.CompareTo(_typeScriptMinVersionForES6Preview) > -1) { - _enableES6Preview = true; + : base("IntelliSense") { + Version version; + var versionString = GetTypeScriptToolsVersion(); + if (!string.IsNullOrEmpty(versionString) && + Version.TryParse(versionString, out version) && + version.CompareTo(_typeScriptMinVersionForES6Preview) > -1) { + _enableES6Preview = true; } } @@ -56,8 +56,8 @@ protected override System.Windows.Forms.IWin32Window Window { } return _window; } - } - + } + internal bool EnableES6Preview { get { return _enableES6Preview; } } internal bool SaveToDisk { @@ -80,11 +80,11 @@ internal AnalysisLevel AnalysisLevel { } set { var oldLevel = _level; - _level = value; - - // Fallback to full intellisense (High) if the ES6 intellisense preview isn't enabled - if (_level == AnalysisLevel.Preview && !_enableES6Preview) { - _level = AnalysisLevel.High; + _level = value; + + // Fallback to full intellisense (High) if the ES6 intellisense preview isn't enabled + if (_level == AnalysisLevel.Preview && !_enableES6Preview) { + _level = AnalysisLevel.High; } if (oldLevel != _level) { @@ -112,11 +112,11 @@ internal int AnalysisLogMax { } } - internal bool OnlyTabOrEnterToCommit { - get { - return _onlyTabOrEnterToCommit; - } - set { + internal bool OnlyTabOrEnterToCommit { + get { + return _onlyTabOrEnterToCommit; + } + set { var oldSetting = _onlyTabOrEnterToCommit; _onlyTabOrEnterToCommit = value; if (oldSetting != _onlyTabOrEnterToCommit) { @@ -124,30 +124,30 @@ internal bool OnlyTabOrEnterToCommit { if (changed != null) { changed(this, EventArgs.Empty); } - } - } + } + } } - internal bool ShowCompletionListAfterCharacterTyped { - get { - return _showCompletionListAfterCharacterTyped; - } - set { - var oldSetting = _showCompletionListAfterCharacterTyped; - _showCompletionListAfterCharacterTyped = value; + internal bool ShowCompletionListAfterCharacterTyped { + get { + return _showCompletionListAfterCharacterTyped; + } + set { + var oldSetting = _showCompletionListAfterCharacterTyped; + _showCompletionListAfterCharacterTyped = value; if (oldSetting != _showCompletionListAfterCharacterTyped) { var changed = ShowCompletionListAfterCharacterTypedChanged; if (changed != null) { changed(this, EventArgs.Empty); } - } - } + } + } } public event EventHandler AnalysisLevelChanged; - public event EventHandler AnalysisLogMaximumChanged; - public event EventHandler SaveToDiskChanged; - public event EventHandler OnlyTabOrEnterToCommitChanged; + public event EventHandler AnalysisLogMaximumChanged; + public event EventHandler SaveToDiskChanged; + public event EventHandler OnlyTabOrEnterToCommitChanged; public event EventHandler ShowCompletionListAfterCharacterTypedChanged; /// @@ -162,7 +162,7 @@ public override void ResetSettings() { private const string AnalysisLevelSetting = "AnalysisLevel"; private const string AnalysisLogMaximumSetting = "AnalysisLogMaximum"; - private const string SaveToDiskSetting = "SaveToDisk"; + private const string SaveToDiskSetting = "SaveToDisk"; private const string OnlyTabOrEnterToCommitSetting = "OnlyTabOrEnterToCommit"; private const string ShowCompletionListAfterCharacterTypedSetting = "ShowCompletionListAfterCharacterTyped"; @@ -177,11 +177,11 @@ public override void LoadSettingsFromStorage() { // Synchronize UI with backing properties. if (_window != null) { _window.SyncControlWithPageSettings(this); - } - - // Settings values can change after loading them from storage as there - // are conditions which could make them fallback to default values. - // Save the final settings back to storage. + } + + // Settings values can change after loading them from storage as there + // are conditions which could make them fallback to default values. + // Save the final settings back to storage. SaveSettingsToStorage(); } @@ -194,49 +194,49 @@ public override void SaveSettingsToStorage() { // Save settings. SaveEnum(AnalysisLevelSetting, AnalysisLevel); SaveInt(AnalysisLogMaximumSetting, AnalysisLogMax); - SaveBool(SaveToDiskSetting, SaveToDisk); + SaveBool(SaveToDiskSetting, SaveToDisk); SaveBool(OnlyTabOrEnterToCommitSetting, OnlyTabOrEnterToCommit); SaveBool(ShowCompletionListAfterCharacterTypedSetting, ShowCompletionListAfterCharacterTyped); - } - - private string GetTypeScriptToolsVersion() { - if (_toolsVersion == null) { - _toolsVersion = string.Empty; - try { - object installDirAsObject = null; - var shell = NodejsPackage.Instance.GetService(typeof(SVsShell)) as IVsShell; - if (shell != null) { - shell.GetProperty((int)__VSSPROPID.VSSPROPID_InstallDirectory, out installDirAsObject); - } - - var idePath = CommonUtils.NormalizeDirectoryPath((string)installDirAsObject) ?? string.Empty; - if (string.IsNullOrEmpty(idePath)) { - return _toolsVersion; - } - - var typeScriptServicesPath = Path.Combine(idePath, @"CommonExtensions\Microsoft\TypeScript\typescriptServices.js"); - if (!File.Exists(typeScriptServicesPath)) { - return _toolsVersion; - } - - var regex = new Regex(@"toolsVersion = ""(?\d.\d?)"";"); - var fileText = File.ReadAllText(typeScriptServicesPath); - var match = regex.Match(fileText); - - var version = match.Groups["version"].Value; - if (!string.IsNullOrWhiteSpace(version)) { - _toolsVersion = version; - } - } catch (Exception ex) { - if (ex.IsCriticalException()) { - throw; - } - - Debug.WriteLine(string.Format("Failed to obtain TypeScript tools version: {0}", ex.ToString())); - } - } - - return _toolsVersion; + } + + private string GetTypeScriptToolsVersion() { + if (_toolsVersion == null) { + _toolsVersion = string.Empty; + try { + object installDirAsObject = null; + var shell = NodejsPackage.Instance.GetService(typeof(SVsShell)) as IVsShell; + if (shell != null) { + shell.GetProperty((int)__VSSPROPID.VSSPROPID_InstallDirectory, out installDirAsObject); + } + + var idePath = CommonUtils.NormalizeDirectoryPath((string)installDirAsObject) ?? string.Empty; + if (string.IsNullOrEmpty(idePath)) { + return _toolsVersion; + } + + var typeScriptServicesPath = Path.Combine(idePath, @"CommonExtensions\Microsoft\TypeScript\typescriptServices.js"); + if (!File.Exists(typeScriptServicesPath)) { + return _toolsVersion; + } + + var regex = new Regex(@"toolsVersion = ""(?\d.\d?)"";"); + var fileText = File.ReadAllText(typeScriptServicesPath); + var match = regex.Match(fileText); + + var version = match.Groups["version"].Value; + if (!string.IsNullOrWhiteSpace(version)) { + _toolsVersion = version; + } + } catch (Exception ex) { + if (ex.IsCriticalException()) { + throw; + } + + Debug.WriteLine(string.Format("Failed to obtain TypeScript tools version: {0}", ex.ToString())); + } + } + + return _toolsVersion; } } } diff --git a/Nodejs/Product/Nodejs/PkgCmdId.cs b/Nodejs/Product/Nodejs/PkgCmdId.cs index 88af9b2be..d87808e3b 100644 --- a/Nodejs/Product/Nodejs/PkgCmdId.cs +++ b/Nodejs/Product/Nodejs/PkgCmdId.cs @@ -1,49 +1,49 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools { - class PkgCmdId { - public const int cmdidReplWindow = 0x200; - public const int cmdidOpenReplWindow = 0x201; - public const int cmdidOpenRemoteDebugProxyFolder = 0x202; - public const int cmdidSetAsNodejsStartupFile = 0x203; - - public const int cmdidSurveyNews = 0x204; - public const int cmdidImportWizard = 0x205; - public const int cmdidOpenRemoteDebugDocumentation = 0x206; - - public const uint cmdidAzureExplorerAttachNodejsDebugger = 0x207; - - public const int cmdidDiagnostics = 0x208; - public const int cmdidSetAsContent = 0x209; - public const int cmdidSetAsCompile = 0x210; - - public const int cmdidNpmManageModules = 0x300; - public const int cmdidNpmInstallModules = 0x301; - public const int cmdidNpmUpdateModules = 0x302; - public const int cmdidNpmUninstallModule = 0x303; - public const int cmdidNpmInstallSingleMissingModule = 0x304; - public const int cmdidNpmUpdateSingleModule = 0x305; - public const int cmdidNpmOpenModuleHomepage = 0x306; - public const int menuIdNpm = 0x3000; - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools { + class PkgCmdId { + public const int cmdidReplWindow = 0x200; + public const int cmdidOpenReplWindow = 0x201; + public const int cmdidOpenRemoteDebugProxyFolder = 0x202; + public const int cmdidSetAsNodejsStartupFile = 0x203; + + public const int cmdidSurveyNews = 0x204; + public const int cmdidImportWizard = 0x205; + public const int cmdidOpenRemoteDebugDocumentation = 0x206; + + public const uint cmdidAzureExplorerAttachNodejsDebugger = 0x207; + + public const int cmdidDiagnostics = 0x208; + public const int cmdidSetAsContent = 0x209; + public const int cmdidSetAsCompile = 0x210; + + public const int cmdidNpmManageModules = 0x300; + public const int cmdidNpmInstallModules = 0x301; + public const int cmdidNpmUpdateModules = 0x302; + public const int cmdidNpmUninstallModule = 0x303; + public const int cmdidNpmInstallSingleMissingModule = 0x304; + public const int cmdidNpmUpdateSingleModule = 0x305; + public const int cmdidNpmOpenModuleHomepage = 0x306; + public const int menuIdNpm = 0x3000; + } +} diff --git a/Nodejs/Product/Nodejs/Project/AbstractNpmNode.cs b/Nodejs/Product/Nodejs/Project/AbstractNpmNode.cs index f93831817..07f07f2c0 100644 --- a/Nodejs/Product/Nodejs/Project/AbstractNpmNode.cs +++ b/Nodejs/Product/Nodejs/Project/AbstractNpmNode.cs @@ -1,129 +1,129 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudioTools.Project; -#if DEV14_OR_LATER -using Microsoft.VisualStudio.Imaging.Interop; -using Microsoft.VisualStudio.Imaging; -#endif - -namespace Microsoft.NodejsTools.Project { - internal abstract class AbstractNpmNode : HierarchyNode { - protected readonly NodejsProjectNode _projectNode; - - protected AbstractNpmNode(NodejsProjectNode root) - : base(root) { - _projectNode = root; - ExcludeNodeFromScc = true; - } - - #region HierarchyNode implementation - - public override Guid ItemTypeGuid { - get { return VSConstants.GUID_ItemType_VirtualFolder; } - } - - public override Guid MenuGroupId { - get { return Guids.NodejsNpmCmdSet; } - } - - public override int MenuCommandId { - get { return PkgCmdId.menuIdNpm; } - } - - /// - /// Disable inline editing of Caption. - /// - public sealed override string GetEditLabel() { - return null; - } -#if DEV14_OR_LATER - protected override bool SupportsIconMonikers { - get { return true; } - } - - /// - /// Returns the icon to use. - /// - protected override ImageMoniker GetIconMoniker(bool open) { - return KnownMonikers.Reference; - } -#else - public sealed override object GetIconHandle(bool open) { - //We don't want the icon to become an expanded folder 'OpenReferenceFolder' - // Thus we always return 'ReferenceFolder' - return ProjectMgr.GetIconHandleByName(ProjectNode.ImageName.ReferenceFolder); - } -#endif - - protected override NodeProperties CreatePropertiesObject() { - return new NpmNodeProperties(this); - } -#endregion - - public abstract void ManageNpmModules(); - - protected void ReloadHierarchy(HierarchyNode parent, IEnumerable modules) { - // We're going to reuse nodes for which matching modules exist in the new set. - // The reason for this is that we want to preserve the expansion state of the - // hierarchy. If we just bin everything off and recreate it all from scratch - // it'll all be in the collapsed state, which will be annoying for users who - // have drilled down into the hierarchy - var recycle = new Dictionary(); - var remove = new List(); - for (var current = parent.FirstChild; null != current; current = current.NextSibling) { - var dep = current as DependencyNode; - if (null == dep) { - if (!(current is GlobalModulesNode) && !(current is LocalModulesNode)) { - remove.Add(current); - } - continue; - } - - if (modules != null && modules.Any( - module => - module.Name == dep.Package.Name - && module.Version == dep.Package.Version - && module.IsBundledDependency == dep.Package.IsBundledDependency - && module.IsDevDependency == dep.Package.IsDevDependency - && module.IsListedInParentPackageJson == dep.Package.IsListedInParentPackageJson - && module.IsMissing == dep.Package.IsMissing - && module.IsOptionalDependency == dep.Package.IsOptionalDependency)) { - recycle[dep.Package.Name] = dep; - } else { - remove.Add(current); - } - } - - foreach (var obsolete in remove) { - parent.RemoveChild(obsolete); - ProjectMgr.OnItemDeleted(obsolete); - } - - if (modules != null) { - foreach (var package in modules) { - DependencyNode child; - - if (recycle.ContainsKey(package.Name)) { - child = recycle[package.Name]; - child.Package = package; - } - else { - child = new DependencyNode(_projectNode, parent as DependencyNode, package); - parent.AddChild(child); - } - - ReloadHierarchy(child, package.Modules); - if (ProjectMgr.ParentHierarchy != null) { - child.ExpandItem(EXPANDFLAGS.EXPF_CollapseFolder); - } - } - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudioTools.Project; +#if DEV14_OR_LATER +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Imaging; +#endif + +namespace Microsoft.NodejsTools.Project { + internal abstract class AbstractNpmNode : HierarchyNode { + protected readonly NodejsProjectNode _projectNode; + + protected AbstractNpmNode(NodejsProjectNode root) + : base(root) { + _projectNode = root; + ExcludeNodeFromScc = true; + } + + #region HierarchyNode implementation + + public override Guid ItemTypeGuid { + get { return VSConstants.GUID_ItemType_VirtualFolder; } + } + + public override Guid MenuGroupId { + get { return Guids.NodejsNpmCmdSet; } + } + + public override int MenuCommandId { + get { return PkgCmdId.menuIdNpm; } + } + + /// + /// Disable inline editing of Caption. + /// + public sealed override string GetEditLabel() { + return null; + } +#if DEV14_OR_LATER + protected override bool SupportsIconMonikers { + get { return true; } + } + + /// + /// Returns the icon to use. + /// + protected override ImageMoniker GetIconMoniker(bool open) { + return KnownMonikers.Reference; + } +#else + public sealed override object GetIconHandle(bool open) { + //We don't want the icon to become an expanded folder 'OpenReferenceFolder' + // Thus we always return 'ReferenceFolder' + return ProjectMgr.GetIconHandleByName(ProjectNode.ImageName.ReferenceFolder); + } +#endif + + protected override NodeProperties CreatePropertiesObject() { + return new NpmNodeProperties(this); + } +#endregion + + public abstract void ManageNpmModules(); + + protected void ReloadHierarchy(HierarchyNode parent, IEnumerable modules) { + // We're going to reuse nodes for which matching modules exist in the new set. + // The reason for this is that we want to preserve the expansion state of the + // hierarchy. If we just bin everything off and recreate it all from scratch + // it'll all be in the collapsed state, which will be annoying for users who + // have drilled down into the hierarchy + var recycle = new Dictionary(); + var remove = new List(); + for (var current = parent.FirstChild; null != current; current = current.NextSibling) { + var dep = current as DependencyNode; + if (null == dep) { + if (!(current is GlobalModulesNode) && !(current is LocalModulesNode)) { + remove.Add(current); + } + continue; + } + + if (modules != null && modules.Any( + module => + module.Name == dep.Package.Name + && module.Version == dep.Package.Version + && module.IsBundledDependency == dep.Package.IsBundledDependency + && module.IsDevDependency == dep.Package.IsDevDependency + && module.IsListedInParentPackageJson == dep.Package.IsListedInParentPackageJson + && module.IsMissing == dep.Package.IsMissing + && module.IsOptionalDependency == dep.Package.IsOptionalDependency)) { + recycle[dep.Package.Name] = dep; + } else { + remove.Add(current); + } + } + + foreach (var obsolete in remove) { + parent.RemoveChild(obsolete); + ProjectMgr.OnItemDeleted(obsolete); + } + + if (modules != null) { + foreach (var package in modules) { + DependencyNode child; + + if (recycle.ContainsKey(package.Name)) { + child = recycle[package.Name]; + child.Package = package; + } + else { + child = new DependencyNode(_projectNode, parent as DependencyNode, package); + parent.AddChild(child); + } + + ReloadHierarchy(child, package.Modules); + if (ProjectMgr.ParentHierarchy != null) { + child.ExpandItem(EXPANDFLAGS.EXPF_CollapseFolder); + } + } + } + } + } +} diff --git a/Nodejs/Product/Nodejs/Project/Attributes.cs b/Nodejs/Product/Nodejs/Project/Attributes.cs index d23988227..0afe08354 100644 --- a/Nodejs/Product/Nodejs/Project/Attributes.cs +++ b/Nodejs/Product/Nodejs/Project/Attributes.cs @@ -1,65 +1,65 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.ComponentModel; - -namespace Microsoft.NodejsTools.Project { - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)] - internal sealed class SRDisplayNameAttribute : DisplayNameAttribute { - string _name; - - public SRDisplayNameAttribute(string name) { - _name = name; - } - - public override string DisplayName { - get { - return SR.GetString(_name); - } - } - } - - [AttributeUsage(AttributeTargets.All)] - internal sealed class SRDescriptionAttribute : DescriptionAttribute { - private bool _replaced; - - public SRDescriptionAttribute(string description) - : base(description) { - } - - public override string Description { - get { - if (!_replaced) { - _replaced = true; - DescriptionValue = SR.GetString(base.Description); - } - return base.Description; - } - } - } - - [AttributeUsage(AttributeTargets.All)] - internal sealed class SRCategoryAttribute : CategoryAttribute { - public SRCategoryAttribute(string category) - : base(category) { - } - - protected override string GetLocalizedString(string value) { - return SR.GetString(value); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.ComponentModel; + +namespace Microsoft.NodejsTools.Project { + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)] + internal sealed class SRDisplayNameAttribute : DisplayNameAttribute { + string _name; + + public SRDisplayNameAttribute(string name) { + _name = name; + } + + public override string DisplayName { + get { + return SR.GetString(_name); + } + } + } + + [AttributeUsage(AttributeTargets.All)] + internal sealed class SRDescriptionAttribute : DescriptionAttribute { + private bool _replaced; + + public SRDescriptionAttribute(string description) + : base(description) { + } + + public override string Description { + get { + if (!_replaced) { + _replaced = true; + DescriptionValue = SR.GetString(base.Description); + } + return base.Description; + } + } + } + + [AttributeUsage(AttributeTargets.All)] + internal sealed class SRCategoryAttribute : CategoryAttribute { + public SRCategoryAttribute(string category) + : base(category) { + } + + protected override string GetLocalizedString(string value) { + return SR.GetString(value); + } + } +} diff --git a/Nodejs/Product/Nodejs/Project/DependencyNode.cs b/Nodejs/Product/Nodejs/Project/DependencyNode.cs index 20dfbfa35..6ce8dc3bc 100644 --- a/Nodejs/Product/Nodejs/Project/DependencyNode.cs +++ b/Nodejs/Product/Nodejs/Project/DependencyNode.cs @@ -1,286 +1,286 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Text; -using System.Windows.Forms; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.Project; -using VsCommands2K = Microsoft.VisualStudio.VSConstants.VSStd2KCmdID; - -namespace Microsoft.NodejsTools.Project { - internal class DependencyNode : HierarchyNode { - private readonly NodejsProjectNode _projectNode; - private readonly DependencyNode _parent; - private readonly string _displayString; - - public DependencyNode( - NodejsProjectNode root, - DependencyNode parent, - IPackage package) - : base(root) { - _projectNode = root; - _parent = parent; - Package = package; - - var buff = new StringBuilder(package.Name); - if (package.IsMissing) { - buff.Append(" (missing)"); - } else { - buff.Append('@'); - buff.Append(package.Version); - - if (!package.IsListedInParentPackageJson) { - buff.AppendFormat(" (not listed in {0})", NodejsConstants.PackageJsonFile); - } else { - List dependencyTypes = new List(3); - if (package.IsDependency) { - dependencyTypes.Add("standard"); - } - if (package.IsDevDependency) { - dependencyTypes.Add("dev"); - } - if (package.IsOptionalDependency) { - dependencyTypes.Add("optional"); - } - - if (package.IsDevDependency || package.IsOptionalDependency) { - buff.Append(" ("); - buff.Append(string.Join(", ", dependencyTypes.ToArray())); - buff.Append(")"); - } - } - } - - if (package.IsBundledDependency) { - buff.Append("[bundled]"); - } - - _displayString = buff.ToString(); - ExcludeNodeFromScc = true; - } - - public IPackage Package { get; internal set; } - - internal INpmController NpmController { - get { - if (null != _projectNode) { - var modulesNode = _projectNode.ModulesNode; - if (null != modulesNode) { - return modulesNode.NpmController; - } - } - return null; - } - } - - #region HierarchyNode implementation - - private string GetRelativeUrlFragment() { - var buff = new StringBuilder(); - if (null != _parent) { - buff.Append(_parent.GetRelativeUrlFragment()); - buff.Append('/'); - } - buff.Append("node_modules/"); - buff.Append(Package.Name); - return buff.ToString(); - } - - public override string Url { - get { return new Url(ProjectMgr.BaseURI, GetRelativeUrlFragment()).AbsoluteUrl; } - } - - public override string Caption { - get { return _displayString; } - } - - public override Guid ItemTypeGuid { - get { return VSConstants.GUID_ItemType_VirtualFolder; } - } - - public override Guid MenuGroupId { - get { return Guids.NodejsNpmCmdSet; } - } - - public override int MenuCommandId { - get { return PkgCmdId.menuIdNpm; } - } - -#if DEV14_OR_LATER - [Obsolete] -#endif - public override object GetIconHandle(bool open) { - int imageIndex = _projectNode.ImageIndexFromNameDictionary[NodejsProjectImageName.Dependency]; - if (Package.IsMissing) { - imageIndex = _projectNode.ImageIndexFromNameDictionary[NodejsProjectImageName.DependencyMissing]; - } else { - if (!Package.IsListedInParentPackageJson) { - imageIndex = _projectNode.ImageIndexFromNameDictionary[NodejsProjectImageName.DependencyNotListed]; - } else { - imageIndex = _projectNode.ImageIndexFromNameDictionary[NodejsProjectImageName.Dependency]; - } - } - - return _projectNode.ImageHandler.GetIconHandle(imageIndex); - } - - public override string GetEditLabel() { - return null; - } - - protected override NodeProperties CreatePropertiesObject() { - return new DependencyNodeProperties(this); - } - - internal DependencyNodeProperties GetPropertiesObject() { - return CreatePropertiesObject() as DependencyNodeProperties; - } - -#endregion - -#region Command handling - - internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { - // Latter condition is because it's only valid to carry out npm operations - // on top level dependencies of the user's project, not sub-dependencies. - // Performing operations on sub-dependencies would just break things. - if (cmdGroup == Guids.NodejsNpmCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidNpmOpenModuleHomepage: - if (this.Package.Homepages != null) { - using (var enumerator = this.Package.Homepages.GetEnumerator()) { - if (enumerator.MoveNext() && !string.IsNullOrEmpty(enumerator.Current)) { - result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; - } else { - result = QueryStatusResult.SUPPORTED; - } - } - } - return VSConstants.S_OK; - } - - if (null == _parent) { - switch (cmd) { - case PkgCmdId.cmdidNpmInstallSingleMissingModule: - if (GetPropertiesObject().IsGlobalInstall) { - result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE; - } else if (null == _projectNode.ModulesNode - || _projectNode.ModulesNode.IsCurrentStateASuppressCommandsMode()) { - result = QueryStatusResult.SUPPORTED; - } else { - if (null != Package && Package.IsMissing) { - result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; - } else { - result = QueryStatusResult.SUPPORTED; - } - } - return VSConstants.S_OK; - - case PkgCmdId.cmdidNpmUpdateSingleModule: - case PkgCmdId.cmdidNpmUninstallModule: - if (null != _projectNode.ModulesNode && - !_projectNode.ModulesNode.IsCurrentStateASuppressCommandsMode()) { - result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; - } else { - result = QueryStatusResult.SUPPORTED; - } - return VSConstants.S_OK; - - case PkgCmdId.cmdidNpmInstallModules: - case PkgCmdId.cmdidNpmUpdateModules: - result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE; - return VSConstants.S_OK; - } - } - } else if (cmdGroup == Microsoft.VisualStudioTools.Project.VsMenus.guidStandardCommandSet2K) { - switch ((VsCommands2K)cmd) { - case CommonConstants.OpenFolderInExplorerCmdId: - result = QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; - return VSConstants.S_OK; - } - } - - return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); - } - - internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { - if (cmdGroup == Guids.NodejsNpmCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidNpmOpenModuleHomepage: - if (this.Package.Homepages != null) { - using (var enumerator = this.Package.Homepages.GetEnumerator()) { - if (enumerator.MoveNext() && !string.IsNullOrEmpty(enumerator.Current)) { - Process.Start(enumerator.Current); - } - } - } - return VSConstants.S_OK; - } - if (null == _parent) { - switch (cmd) { - case PkgCmdId.cmdidNpmInstallSingleMissingModule: - if (null != _projectNode.ModulesNode) { - var t = _projectNode.ModulesNode.InstallMissingModule(this); - } - return VSConstants.S_OK; - - case PkgCmdId.cmdidNpmUninstallModule: - if (null != _projectNode.ModulesNode) { - var t = _projectNode.ModulesNode.UninstallModule(this); - } - return VSConstants.S_OK; - - case PkgCmdId.cmdidNpmUpdateSingleModule: - if (null != _projectNode.ModulesNode) { - var t = _projectNode.ModulesNode.UpdateModule(this); - } - return VSConstants.S_OK; - } - } - } else if (cmdGroup == Microsoft.VisualStudioTools.Project.VsMenus.guidStandardCommandSet2K) { - switch ((VsCommands2K)cmd) { - case CommonConstants.OpenFolderInExplorerCmdId: - string path = this.Package.Path; - try { - Process.Start(path); - } catch (Exception ex) { - if (ex is InvalidOperationException || ex is Win32Exception) { - MessageBox.Show( - String.Format("Path to module does not exist:\n {0}", path), - SR.ProductName, - MessageBoxButtons.OK, - MessageBoxIcon.Error); - return VSConstants.S_FALSE; - } - throw; - } - return VSConstants.S_OK; - } - } - - return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); - } - -#endregion - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Text; +using System.Windows.Forms; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.Project; +using VsCommands2K = Microsoft.VisualStudio.VSConstants.VSStd2KCmdID; + +namespace Microsoft.NodejsTools.Project { + internal class DependencyNode : HierarchyNode { + private readonly NodejsProjectNode _projectNode; + private readonly DependencyNode _parent; + private readonly string _displayString; + + public DependencyNode( + NodejsProjectNode root, + DependencyNode parent, + IPackage package) + : base(root) { + _projectNode = root; + _parent = parent; + Package = package; + + var buff = new StringBuilder(package.Name); + if (package.IsMissing) { + buff.Append(" (missing)"); + } else { + buff.Append('@'); + buff.Append(package.Version); + + if (!package.IsListedInParentPackageJson) { + buff.AppendFormat(" (not listed in {0})", NodejsConstants.PackageJsonFile); + } else { + List dependencyTypes = new List(3); + if (package.IsDependency) { + dependencyTypes.Add("standard"); + } + if (package.IsDevDependency) { + dependencyTypes.Add("dev"); + } + if (package.IsOptionalDependency) { + dependencyTypes.Add("optional"); + } + + if (package.IsDevDependency || package.IsOptionalDependency) { + buff.Append(" ("); + buff.Append(string.Join(", ", dependencyTypes.ToArray())); + buff.Append(")"); + } + } + } + + if (package.IsBundledDependency) { + buff.Append("[bundled]"); + } + + _displayString = buff.ToString(); + ExcludeNodeFromScc = true; + } + + public IPackage Package { get; internal set; } + + internal INpmController NpmController { + get { + if (null != _projectNode) { + var modulesNode = _projectNode.ModulesNode; + if (null != modulesNode) { + return modulesNode.NpmController; + } + } + return null; + } + } + + #region HierarchyNode implementation + + private string GetRelativeUrlFragment() { + var buff = new StringBuilder(); + if (null != _parent) { + buff.Append(_parent.GetRelativeUrlFragment()); + buff.Append('/'); + } + buff.Append("node_modules/"); + buff.Append(Package.Name); + return buff.ToString(); + } + + public override string Url { + get { return new Url(ProjectMgr.BaseURI, GetRelativeUrlFragment()).AbsoluteUrl; } + } + + public override string Caption { + get { return _displayString; } + } + + public override Guid ItemTypeGuid { + get { return VSConstants.GUID_ItemType_VirtualFolder; } + } + + public override Guid MenuGroupId { + get { return Guids.NodejsNpmCmdSet; } + } + + public override int MenuCommandId { + get { return PkgCmdId.menuIdNpm; } + } + +#if DEV14_OR_LATER + [Obsolete] +#endif + public override object GetIconHandle(bool open) { + int imageIndex = _projectNode.ImageIndexFromNameDictionary[NodejsProjectImageName.Dependency]; + if (Package.IsMissing) { + imageIndex = _projectNode.ImageIndexFromNameDictionary[NodejsProjectImageName.DependencyMissing]; + } else { + if (!Package.IsListedInParentPackageJson) { + imageIndex = _projectNode.ImageIndexFromNameDictionary[NodejsProjectImageName.DependencyNotListed]; + } else { + imageIndex = _projectNode.ImageIndexFromNameDictionary[NodejsProjectImageName.Dependency]; + } + } + + return _projectNode.ImageHandler.GetIconHandle(imageIndex); + } + + public override string GetEditLabel() { + return null; + } + + protected override NodeProperties CreatePropertiesObject() { + return new DependencyNodeProperties(this); + } + + internal DependencyNodeProperties GetPropertiesObject() { + return CreatePropertiesObject() as DependencyNodeProperties; + } + +#endregion + +#region Command handling + + internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { + // Latter condition is because it's only valid to carry out npm operations + // on top level dependencies of the user's project, not sub-dependencies. + // Performing operations on sub-dependencies would just break things. + if (cmdGroup == Guids.NodejsNpmCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidNpmOpenModuleHomepage: + if (this.Package.Homepages != null) { + using (var enumerator = this.Package.Homepages.GetEnumerator()) { + if (enumerator.MoveNext() && !string.IsNullOrEmpty(enumerator.Current)) { + result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; + } else { + result = QueryStatusResult.SUPPORTED; + } + } + } + return VSConstants.S_OK; + } + + if (null == _parent) { + switch (cmd) { + case PkgCmdId.cmdidNpmInstallSingleMissingModule: + if (GetPropertiesObject().IsGlobalInstall) { + result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE; + } else if (null == _projectNode.ModulesNode + || _projectNode.ModulesNode.IsCurrentStateASuppressCommandsMode()) { + result = QueryStatusResult.SUPPORTED; + } else { + if (null != Package && Package.IsMissing) { + result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; + } else { + result = QueryStatusResult.SUPPORTED; + } + } + return VSConstants.S_OK; + + case PkgCmdId.cmdidNpmUpdateSingleModule: + case PkgCmdId.cmdidNpmUninstallModule: + if (null != _projectNode.ModulesNode && + !_projectNode.ModulesNode.IsCurrentStateASuppressCommandsMode()) { + result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; + } else { + result = QueryStatusResult.SUPPORTED; + } + return VSConstants.S_OK; + + case PkgCmdId.cmdidNpmInstallModules: + case PkgCmdId.cmdidNpmUpdateModules: + result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE; + return VSConstants.S_OK; + } + } + } else if (cmdGroup == Microsoft.VisualStudioTools.Project.VsMenus.guidStandardCommandSet2K) { + switch ((VsCommands2K)cmd) { + case CommonConstants.OpenFolderInExplorerCmdId: + result = QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; + return VSConstants.S_OK; + } + } + + return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); + } + + internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + if (cmdGroup == Guids.NodejsNpmCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidNpmOpenModuleHomepage: + if (this.Package.Homepages != null) { + using (var enumerator = this.Package.Homepages.GetEnumerator()) { + if (enumerator.MoveNext() && !string.IsNullOrEmpty(enumerator.Current)) { + Process.Start(enumerator.Current); + } + } + } + return VSConstants.S_OK; + } + if (null == _parent) { + switch (cmd) { + case PkgCmdId.cmdidNpmInstallSingleMissingModule: + if (null != _projectNode.ModulesNode) { + var t = _projectNode.ModulesNode.InstallMissingModule(this); + } + return VSConstants.S_OK; + + case PkgCmdId.cmdidNpmUninstallModule: + if (null != _projectNode.ModulesNode) { + var t = _projectNode.ModulesNode.UninstallModule(this); + } + return VSConstants.S_OK; + + case PkgCmdId.cmdidNpmUpdateSingleModule: + if (null != _projectNode.ModulesNode) { + var t = _projectNode.ModulesNode.UpdateModule(this); + } + return VSConstants.S_OK; + } + } + } else if (cmdGroup == Microsoft.VisualStudioTools.Project.VsMenus.guidStandardCommandSet2K) { + switch ((VsCommands2K)cmd) { + case CommonConstants.OpenFolderInExplorerCmdId: + string path = this.Package.Path; + try { + Process.Start(path); + } catch (Exception ex) { + if (ex is InvalidOperationException || ex is Win32Exception) { + MessageBox.Show( + String.Format("Path to module does not exist:\n {0}", path), + SR.ProductName, + MessageBoxButtons.OK, + MessageBoxIcon.Error); + return VSConstants.S_FALSE; + } + throw; + } + return VSConstants.S_OK; + } + } + + return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); + } + +#endregion + } } \ No newline at end of file diff --git a/Nodejs/Product/Nodejs/Project/DependencyNodeProperties.cs b/Nodejs/Product/Nodejs/Project/DependencyNodeProperties.cs index 8984a1377..030bc8754 100644 --- a/Nodejs/Product/Nodejs/Project/DependencyNodeProperties.cs +++ b/Nodejs/Product/Nodejs/Project/DependencyNodeProperties.cs @@ -1,274 +1,274 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudioTools.Project; - -namespace Microsoft.NodejsTools.Project { - - [ComVisible(true)] - [ClassInterface(ClassInterfaceType.AutoDual)] - [Guid("CA6C9721-2F64-4A1F-99C9-C087F698CB34")] - public class DependencyNodeProperties : NodeProperties { - internal DependencyNodeProperties(DependencyNode node) : base(node) {} - - private DependencyNode DependencyNode { get { return Node as DependencyNode; } } - - private IPackage Package { get { return DependencyNode.Package; } } - - public override string GetClassName() { - return SR.GetString(IsSubPackage - ? (IsGlobalInstall ? SR.PropertiesClassGlobalSubPackage : SR.PropertiesClassLocalSubPackage) - : (IsGlobalInstall ? SR.PropertiesClassGlobalPackage : SR.PropertiesClassLocalPackage) - ); - } - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmPackageName)] - [SRDescriptionAttribute(SR.NpmPackageNameDescription)] - public string PackageName { - get { - return null == Package ? null : Package.Name; - } - } - - [SRCategoryAttribute(SR.CategoryVersion)] - [SRDisplayName(SR.NpmPackageVersion)] - [SRDescriptionAttribute(SR.NpmPackageVersionDescription)] - public string PackageVersion { - get { - return null == Package ? null : Package.Version.ToString(); - } - } - - [SRCategoryAttribute(SR.CategoryVersion)] - [SRDisplayName(SR.NpmPackageRequestedVersionRange)] - [SRDescriptionAttribute(SR.NpmPackageRequestedVersionRangeDescription)] - public string RequestedVersionRange { - get { - var range = null == Package ? null : Package.RequestedVersionRange; - return range ?? SR.GetString(SR.RequestedVersionRangeNone); - } - } - - private IPackageCatalog MostRecentlyLoadedCatalog { - get { - var controller = DependencyNode.NpmController; - return null == controller ? null : controller.MostRecentlyLoadedCatalog; - } - } - - // TODO Retrieving the package information is currently too slow to include in properties pane. - //[SRCategoryAttribute(SR.CategoryVersion)] - //[SRDisplayName(SR.NpmPackageNewVersionAvailable)] - //[SRDescriptionAttribute(SR.NpmPackageNewVersionAvailableDescription)] - //public string NewVersionAvailable { - // get { - // if (IsSubPackage) { - // return SR.GetString(SR.NewVersionNotApplicableSubpackage); - // } - - // var package = Package; - // var catalog = MostRecentlyLoadedCatalog; - // if (null == catalog || null == package) { - // return SR.GetString(SR.NewVersionUnknown); - // } - - // var listed = catalog[package.Name]; - // if (null == listed) { - // return SR.GetString(SR.NewVersionUnknown); - // } - - // return listed.Version > package.Version - // ? SR.GetString(SR.NewVersionYes, listed.Version) - // : SR.GetString(SR.NewVersionNo); - // } - //} - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmPackageDescription)] - [SRDescriptionAttribute(SR.NpmPackageDescriptionDescription)] - public string Description { - get { - return null == Package ? null : Package.Description; - } - } - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmPackageKeywords)] - [SRDescriptionAttribute(SR.NpmPackageKeywordsDescription)] - public string Keywords { - get { - if (null == Package) { - return null; - } - - var buff = new StringBuilder(); - foreach (var keyword in Package.Keywords) { - if (buff.Length > 0) { - buff.Append(", "); - } - buff.Append(keyword); - } - return buff.ToString(); - } - } - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmPackageAuthor)] - [SRDescriptionAttribute(SR.NpmPackageAuthorDescription)] - public string Author { - get { - var author = null == Package ? null : Package.Author; - return null == author ? null : author.ToString(); - } - } - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmPackagePath)] - [SRDescriptionAttribute(SR.NpmPackagePathDescription)] - public string Path { - get { - return null == Package ? null : Package.Path; - } - } - - internal bool IsGlobalInstall { - get { - var node = DependencyNode as HierarchyNode; - while (null != node) { - if (node is GlobalModulesNode) { - return true; - } - - node = node.Parent; - } - return false; - } - } - - internal bool IsSubPackage { - get { - var node = DependencyNode as HierarchyNode; - if (null != node && node.Parent is DependencyNode) { - return true; - } - return false; - } - } - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmPackageType)] - [SRDescriptionAttribute(SR.NpmPackageTypeDescription)] - public string PackageType { - get { - if (IsGlobalInstall) { - return IsSubPackage - ? SR.GetString(SR.PackageTypeGlobalSubpackage) - : SR.GetString(SR.PackageTypeGlobal); - } - - return IsSubPackage - ? SR.GetString(SR.PackageTypeLocalSubpackage) - : SR.GetString(SR.PackageTypeLocal); - } - } - - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmPackageLinkStatus)] - [SRDescriptionAttribute(SR.NpmPackageLinkStatusDescription)] - public string LinkStatus { - get { - if (IsSubPackage) { - return SR.GetString(SR.LinkStatusNotApplicableSubPackages); - } - - var package = Package; - var controller = DependencyNode.NpmController; - if (null != controller && null != package) { - if (IsGlobalInstall) { - var root = controller.RootPackage; - if (null != root) { - var local = root.Modules[package.Name]; - return null == local || local.Version != package.Version - ? SR.GetString(SR.LinkStatusNotLinkedToProject) - : SR.GetString(SR.LinkStatusLinkedToProject); - } - } else { - var global = controller.GlobalPackages; - if (null != global) { - var installed = global.Modules[package.Name]; - return null == installed || installed.Version != package.Version - ? SR.GetString(SR.LinkStatusLocallyInstalled) - : SR.GetString(SR.LinkStatusLinkedFromGlobal); - } - } - } - - return SR.GetString(SR.LinkStatusUnknown); - } - } - - [SRCategoryAttribute(SR.CategoryStatus)] - [SRDisplayName(SR.NpmPackageIsListedInParentPackageJson)] - [SRDescriptionAttribute(SR.NpmPackageIsListedInParentPackageJsonDescription)] - public bool IsListedInParentPackageJson { - get { - return null != Package && Package.IsListedInParentPackageJson; - } - } - - [SRCategoryAttribute(SR.CategoryStatus)] - [SRDisplayName(SR.NpmPackageIsMissing)] - [SRDescriptionAttribute(SR.NpmPackageIsMissingDescription)] - public bool IsMissing { - get { - return null != Package && Package.IsMissing; - } - } - - [SRCategoryAttribute(SR.CategoryStatus)] - [SRDisplayName(SR.NpmPackageIsDevDependency)] - [SRDescriptionAttribute(SR.NpmPackageIsDevDependencyDescription)] - public bool IsDevDependency { - get { - return null != Package && Package.IsDevDependency; - } - } - - [SRCategoryAttribute(SR.CategoryStatus)] - [SRDisplayName(SR.NpmPackageIsOptionalDependency)] - [SRDescriptionAttribute(SR.NpmPackageIsOptionalDependencyDescription)] - public bool IsOptionalDependency { - get { - return null != Package && Package.IsOptionalDependency; - } - } - - [SRCategoryAttribute(SR.CategoryStatus)] - [SRDisplayName(SR.NpmPackageIsBundledDependency)] - [SRDescriptionAttribute(SR.NpmPackageIsBundledDependencyDescription)] - public bool IsBundledDependency { - get { - return null != Package && Package.IsBundledDependency; - } - } - - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Runtime.InteropServices; +using System.Text; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudioTools.Project; + +namespace Microsoft.NodejsTools.Project { + + [ComVisible(true)] + [ClassInterface(ClassInterfaceType.AutoDual)] + [Guid("CA6C9721-2F64-4A1F-99C9-C087F698CB34")] + public class DependencyNodeProperties : NodeProperties { + internal DependencyNodeProperties(DependencyNode node) : base(node) {} + + private DependencyNode DependencyNode { get { return Node as DependencyNode; } } + + private IPackage Package { get { return DependencyNode.Package; } } + + public override string GetClassName() { + return SR.GetString(IsSubPackage + ? (IsGlobalInstall ? SR.PropertiesClassGlobalSubPackage : SR.PropertiesClassLocalSubPackage) + : (IsGlobalInstall ? SR.PropertiesClassGlobalPackage : SR.PropertiesClassLocalPackage) + ); + } + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmPackageName)] + [SRDescriptionAttribute(SR.NpmPackageNameDescription)] + public string PackageName { + get { + return null == Package ? null : Package.Name; + } + } + + [SRCategoryAttribute(SR.CategoryVersion)] + [SRDisplayName(SR.NpmPackageVersion)] + [SRDescriptionAttribute(SR.NpmPackageVersionDescription)] + public string PackageVersion { + get { + return null == Package ? null : Package.Version.ToString(); + } + } + + [SRCategoryAttribute(SR.CategoryVersion)] + [SRDisplayName(SR.NpmPackageRequestedVersionRange)] + [SRDescriptionAttribute(SR.NpmPackageRequestedVersionRangeDescription)] + public string RequestedVersionRange { + get { + var range = null == Package ? null : Package.RequestedVersionRange; + return range ?? SR.GetString(SR.RequestedVersionRangeNone); + } + } + + private IPackageCatalog MostRecentlyLoadedCatalog { + get { + var controller = DependencyNode.NpmController; + return null == controller ? null : controller.MostRecentlyLoadedCatalog; + } + } + + // TODO Retrieving the package information is currently too slow to include in properties pane. + //[SRCategoryAttribute(SR.CategoryVersion)] + //[SRDisplayName(SR.NpmPackageNewVersionAvailable)] + //[SRDescriptionAttribute(SR.NpmPackageNewVersionAvailableDescription)] + //public string NewVersionAvailable { + // get { + // if (IsSubPackage) { + // return SR.GetString(SR.NewVersionNotApplicableSubpackage); + // } + + // var package = Package; + // var catalog = MostRecentlyLoadedCatalog; + // if (null == catalog || null == package) { + // return SR.GetString(SR.NewVersionUnknown); + // } + + // var listed = catalog[package.Name]; + // if (null == listed) { + // return SR.GetString(SR.NewVersionUnknown); + // } + + // return listed.Version > package.Version + // ? SR.GetString(SR.NewVersionYes, listed.Version) + // : SR.GetString(SR.NewVersionNo); + // } + //} + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmPackageDescription)] + [SRDescriptionAttribute(SR.NpmPackageDescriptionDescription)] + public string Description { + get { + return null == Package ? null : Package.Description; + } + } + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmPackageKeywords)] + [SRDescriptionAttribute(SR.NpmPackageKeywordsDescription)] + public string Keywords { + get { + if (null == Package) { + return null; + } + + var buff = new StringBuilder(); + foreach (var keyword in Package.Keywords) { + if (buff.Length > 0) { + buff.Append(", "); + } + buff.Append(keyword); + } + return buff.ToString(); + } + } + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmPackageAuthor)] + [SRDescriptionAttribute(SR.NpmPackageAuthorDescription)] + public string Author { + get { + var author = null == Package ? null : Package.Author; + return null == author ? null : author.ToString(); + } + } + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmPackagePath)] + [SRDescriptionAttribute(SR.NpmPackagePathDescription)] + public string Path { + get { + return null == Package ? null : Package.Path; + } + } + + internal bool IsGlobalInstall { + get { + var node = DependencyNode as HierarchyNode; + while (null != node) { + if (node is GlobalModulesNode) { + return true; + } + + node = node.Parent; + } + return false; + } + } + + internal bool IsSubPackage { + get { + var node = DependencyNode as HierarchyNode; + if (null != node && node.Parent is DependencyNode) { + return true; + } + return false; + } + } + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmPackageType)] + [SRDescriptionAttribute(SR.NpmPackageTypeDescription)] + public string PackageType { + get { + if (IsGlobalInstall) { + return IsSubPackage + ? SR.GetString(SR.PackageTypeGlobalSubpackage) + : SR.GetString(SR.PackageTypeGlobal); + } + + return IsSubPackage + ? SR.GetString(SR.PackageTypeLocalSubpackage) + : SR.GetString(SR.PackageTypeLocal); + } + } + + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmPackageLinkStatus)] + [SRDescriptionAttribute(SR.NpmPackageLinkStatusDescription)] + public string LinkStatus { + get { + if (IsSubPackage) { + return SR.GetString(SR.LinkStatusNotApplicableSubPackages); + } + + var package = Package; + var controller = DependencyNode.NpmController; + if (null != controller && null != package) { + if (IsGlobalInstall) { + var root = controller.RootPackage; + if (null != root) { + var local = root.Modules[package.Name]; + return null == local || local.Version != package.Version + ? SR.GetString(SR.LinkStatusNotLinkedToProject) + : SR.GetString(SR.LinkStatusLinkedToProject); + } + } else { + var global = controller.GlobalPackages; + if (null != global) { + var installed = global.Modules[package.Name]; + return null == installed || installed.Version != package.Version + ? SR.GetString(SR.LinkStatusLocallyInstalled) + : SR.GetString(SR.LinkStatusLinkedFromGlobal); + } + } + } + + return SR.GetString(SR.LinkStatusUnknown); + } + } + + [SRCategoryAttribute(SR.CategoryStatus)] + [SRDisplayName(SR.NpmPackageIsListedInParentPackageJson)] + [SRDescriptionAttribute(SR.NpmPackageIsListedInParentPackageJsonDescription)] + public bool IsListedInParentPackageJson { + get { + return null != Package && Package.IsListedInParentPackageJson; + } + } + + [SRCategoryAttribute(SR.CategoryStatus)] + [SRDisplayName(SR.NpmPackageIsMissing)] + [SRDescriptionAttribute(SR.NpmPackageIsMissingDescription)] + public bool IsMissing { + get { + return null != Package && Package.IsMissing; + } + } + + [SRCategoryAttribute(SR.CategoryStatus)] + [SRDisplayName(SR.NpmPackageIsDevDependency)] + [SRDescriptionAttribute(SR.NpmPackageIsDevDependencyDescription)] + public bool IsDevDependency { + get { + return null != Package && Package.IsDevDependency; + } + } + + [SRCategoryAttribute(SR.CategoryStatus)] + [SRDisplayName(SR.NpmPackageIsOptionalDependency)] + [SRDescriptionAttribute(SR.NpmPackageIsOptionalDependencyDescription)] + public bool IsOptionalDependency { + get { + return null != Package && Package.IsOptionalDependency; + } + } + + [SRCategoryAttribute(SR.CategoryStatus)] + [SRDisplayName(SR.NpmPackageIsBundledDependency)] + [SRDescriptionAttribute(SR.NpmPackageIsBundledDependencyDescription)] + public bool IsBundledDependency { + get { + return null != Package && Package.IsBundledDependency; + } + } + + } +} diff --git a/Nodejs/Product/Nodejs/Project/GlobalModulesNode.cs b/Nodejs/Product/Nodejs/Project/GlobalModulesNode.cs index 64e6aa0fc..4d746b975 100644 --- a/Nodejs/Product/Nodejs/Project/GlobalModulesNode.cs +++ b/Nodejs/Product/Nodejs/Project/GlobalModulesNode.cs @@ -1,101 +1,101 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Linq; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio; -using Microsoft.VisualStudioTools.Project; - -namespace Microsoft.NodejsTools.Project { - internal class GlobalModulesNode : AbstractNpmNode { - - /// - /// The caption to display for this node - /// - private const string _cCaption = "global"; - - /// - /// The virtual name of this node. - /// - public const string GlobalModulesVirtualName = "GlobalModules"; - - private NodeModulesNode _parent; - - public GlobalModulesNode(NodejsProjectNode root, NodeModulesNode parent) - : base(root) { - _parent = parent; - } - - public override string Url { - get { return GlobalModulesVirtualName; } - } - - public override string Caption { // TODO: stick this string in a resource, along with the NodeModulesNode caption - get { return _cCaption; } - } - - public override int SortPriority { - get { return -1; /* DefaultSortOrderNode.FolderNode; */ } - } - - internal IGlobalPackages GlobalPackages { get; set; } - - internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { - if (cmdGroup == Guids.NodejsNpmCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidNpmUpdateModules: - if (_parent.IsCurrentStateASuppressCommandsMode()) { - result = QueryStatusResult.SUPPORTED; - } else { - if (AllChildren.Any()) { - result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; - } else { - result = QueryStatusResult.SUPPORTED; - } - } - return VSConstants.S_OK; - - case PkgCmdId.cmdidNpmInstallModules: - case PkgCmdId.cmdidNpmInstallSingleMissingModule: - case PkgCmdId.cmdidNpmUninstallModule: - case PkgCmdId.cmdidNpmUpdateSingleModule: - case PkgCmdId.cmdidNpmOpenModuleHomepage: - result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE; - return VSConstants.S_OK; - } - } - - return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); - } - - internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { - if (cmdGroup == Guids.NodejsNpmCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidNpmUpdateModules: - var t = _parent.UpdateModules(AllChildren.ToList()); - return VSConstants.S_OK; - } - } - - return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); - } - - public override void ManageNpmModules() { - _parent.ManageModules(isGlobal:true); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Linq; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio; +using Microsoft.VisualStudioTools.Project; + +namespace Microsoft.NodejsTools.Project { + internal class GlobalModulesNode : AbstractNpmNode { + + /// + /// The caption to display for this node + /// + private const string _cCaption = "global"; + + /// + /// The virtual name of this node. + /// + public const string GlobalModulesVirtualName = "GlobalModules"; + + private NodeModulesNode _parent; + + public GlobalModulesNode(NodejsProjectNode root, NodeModulesNode parent) + : base(root) { + _parent = parent; + } + + public override string Url { + get { return GlobalModulesVirtualName; } + } + + public override string Caption { // TODO: stick this string in a resource, along with the NodeModulesNode caption + get { return _cCaption; } + } + + public override int SortPriority { + get { return -1; /* DefaultSortOrderNode.FolderNode; */ } + } + + internal IGlobalPackages GlobalPackages { get; set; } + + internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { + if (cmdGroup == Guids.NodejsNpmCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidNpmUpdateModules: + if (_parent.IsCurrentStateASuppressCommandsMode()) { + result = QueryStatusResult.SUPPORTED; + } else { + if (AllChildren.Any()) { + result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; + } else { + result = QueryStatusResult.SUPPORTED; + } + } + return VSConstants.S_OK; + + case PkgCmdId.cmdidNpmInstallModules: + case PkgCmdId.cmdidNpmInstallSingleMissingModule: + case PkgCmdId.cmdidNpmUninstallModule: + case PkgCmdId.cmdidNpmUpdateSingleModule: + case PkgCmdId.cmdidNpmOpenModuleHomepage: + result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE; + return VSConstants.S_OK; + } + } + + return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); + } + + internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + if (cmdGroup == Guids.NodejsNpmCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidNpmUpdateModules: + var t = _parent.UpdateModules(AllChildren.ToList()); + return VSConstants.S_OK; + } + } + + return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); + } + + public override void ManageNpmModules() { + _parent.ManageModules(isGlobal:true); + } + } +} diff --git a/Nodejs/Product/Nodejs/Project/NodeModulesNode.cs b/Nodejs/Product/Nodejs/Project/NodeModulesNode.cs index afb046e18..4eeeb59aa 100644 --- a/Nodejs/Product/Nodejs/Project/NodeModulesNode.cs +++ b/Nodejs/Product/Nodejs/Project/NodeModulesNode.cs @@ -1,692 +1,692 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using Microsoft.NodejsTools.Npm; -using Microsoft.NodejsTools.NpmUI; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.Project; -using MessageBox = System.Windows.MessageBox; -using Timer = System.Threading.Timer; - -namespace Microsoft.NodejsTools.Project { - internal class NodeModulesNode : AbstractNpmNode { - #region Constants - - /// - /// The caption to display for this node - /// - private const string _cCaption = "npm"; - - /// - /// The virtual name of this node. - /// - public const string NodeModulesVirtualName = "NodeModules"; - - #endregion - - #region Member variables - - private readonly GlobalModulesNode _globalModulesNode; - private readonly LocalModulesNode _devModulesNode; - private readonly LocalModulesNode _optionalModulesNode; - - private Timer _npmIdleTimer; - private INpmController _npmController; - private int _npmCommandsExecuting; - private bool _suppressCommands; - private bool _firstHierarchyLoad = true; - - private readonly object _commandCountLock = new object(); - - private bool _isDisposed; - - #endregion - - #region Initialization - - public NodeModulesNode(NodejsProjectNode root) - : base(root) { - CreateNpmController(); - - _globalModulesNode = new GlobalModulesNode(root, this); - AddChild(_globalModulesNode); - - _devModulesNode = new LocalModulesNode(root, this, "dev", "DevelopmentModules", DependencyType.Development); - AddChild(_devModulesNode); - - _optionalModulesNode = new LocalModulesNode(root, this, "optional", "OptionalModules", DependencyType.Optional); - AddChild(_optionalModulesNode); - } - - private void CheckNotDisposed() { - if (_isDisposed) { - throw new ObjectDisposedException( - "This NodeModulesNode has been disposed of and should no longer be used."); - } - } - - protected override void Dispose(bool disposing) { - if (!_isDisposed) { - _npmController.Dispose(); - - if (null != _npmIdleTimer) { - _npmIdleTimer.Dispose(); - _npmIdleTimer = null; - } - - if (null != _npmController) { - _npmController.CommandStarted -= NpmController_CommandStarted; - _npmController.OutputLogged -= NpmController_OutputLogged; - _npmController.ErrorLogged -= NpmController_ErrorLogged; - _npmController.ExceptionLogged -= NpmController_ExceptionLogged; - _npmController.CommandCompleted -= NpmController_CommandCompleted; - } - - _isDisposed = true; - } - - base.Dispose(disposing); - } - - #endregion - - #region Properties - - private string GetNpmPathFromNodePathInProject() { - var props = ProjectMgr.NodeProperties as NodejsProjectNodeProperties; - return NpmHelpers.GetPathToNpm(props != null ? props.NodeExePath : null); - } - - private class NpmPathProvider : INpmPathProvider { - private NodeModulesNode _owner; - internal NpmPathProvider(NodeModulesNode owner) { - _owner = owner; - } - - public string PathToNpm { - get { - return _owner.GetNpmPathFromNodePathInProject(); - } - } - } - - private INpmController CreateNpmController() { - if (null == _npmController) { - _npmController = NpmControllerFactory.Create( - _projectNode.ProjectHome, - NodejsPackage.Instance.NpmOptionsPage.NpmCachePath, - false, - new NpmPathProvider(this)); - _npmController.CommandStarted += NpmController_CommandStarted; - _npmController.OutputLogged += NpmController_OutputLogged; - _npmController.ErrorLogged += NpmController_ErrorLogged; - _npmController.ExceptionLogged += NpmController_ExceptionLogged; - _npmController.CommandCompleted += NpmController_CommandCompleted; - } - return _npmController; - } - - void NpmController_FinishedRefresh(object sender, EventArgs e) { - ReloadHierarchySafe(); - } - - public INpmController NpmController { - get { - return _npmController; - } - } - - internal IRootPackage RootPackage { - get { - var controller = NpmController; - return null == controller ? null : controller.RootPackage; - } - } - - private INodeModules RootModules { - get { - var root = RootPackage; - return null == root ? null : root.Modules; - } - } - - private bool HasMissingModules { - get { - var modules = RootModules; - return null != modules && modules.HasMissingModules; - } - } - - private bool HasModules { - get { - var modules = RootModules; - return null != modules && modules.Count > 0; - } - } - - #endregion - - #region Logging and status bar updates - -#if DEV14_OR_LATER - // This is the package manager pane that ships with VS2015, and we should print there if available. - private static readonly Guid VSPackageManagerPaneGuid = new Guid("C7E31C31-1451-4E05-B6BE-D11B6829E8BB"); -#else - private static readonly Guid NpmOutputPaneGuid = new Guid("25764421-33B8-4163-BD02-A94E299D52D8"); -#endif - - private OutputWindowRedirector GetNpmOutputPane() { - try { -#if DEV14_OR_LATER - return OutputWindowRedirector.Get(_projectNode.Site, VSPackageManagerPaneGuid, "Bower/npm"); -#else - return OutputWindowRedirector.Get(_projectNode.Site, NpmOutputPaneGuid, SR.GetString(SR.NpmOutputPaneTitle)); -#endif - } catch (InvalidOperationException) { - return null; - } - } - - private void ConditionallyShowNpmOutputPane() { - if (NodejsPackage.Instance.NpmOptionsPage.ShowOutputWindowWhenExecutingNpm) { - var pane = GetNpmOutputPane(); - if (null != pane) { - pane.ShowAndActivate(); - } - } - } - -#if INTEGRATE_WITH_ERROR_LIST - - private ErrorListProvider _errorListProvider; - - private ErrorListProvider GetErrorListProvider() { - if (null == _errorListProvider) { - _errorListProvider = new ErrorListProvider(_projectNode.ProjectMgr.Site); - } - return _errorListProvider; - } - - private void WriteNpmErrorsToErrorList(NpmLogEventArgs args) { - var provider = GetErrorListProvider(); - foreach (var line in args.LogText.Split(new[] {'\n' })) { - var trimmed = line.Trim(); - if (trimmed.StartsWith("npm ERR!")) { - provider.Tasks.Add(new ErrorTask() { - Category = TaskCategory.User, - ErrorCategory = TaskErrorCategory.Error, - Text = trimmed - }); - } else if (trimmed.StartsWith("npm WARN")) { - provider.Tasks.Add(new ErrorTask() { - Category = TaskCategory.User, - ErrorCategory = TaskErrorCategory.Warning, - Text = trimmed - }); - } - } - } - -#endif - - private void ForceUpdateStatusBarWithNpmActivity(string activity) { - if (string.IsNullOrEmpty(activity) || string.IsNullOrEmpty(activity.Trim())) { - return; - } - - if (!activity.Contains("npm")) { - activity = string.Format("npm: {0}", activity); - } - - var statusBar = (IVsStatusbar)_projectNode.GetService(typeof(SVsStatusbar)); - if (null != statusBar) { - statusBar.SetText(activity); - } - } - - private void ForceUpdateStatusBarWithNpmActivitySafe(string activity) { - ProjectMgr.Site.GetUIThread().InvokeAsync(() => ForceUpdateStatusBarWithNpmActivity(activity)) - .HandleAllExceptions(SR.ProductName) - .DoNotWait(); - } - - private void UpdateStatusBarWithNpmActivity(string activity) { - lock (_commandCountLock) { - if (_npmCommandsExecuting == 0) { - return; - } - } - - ForceUpdateStatusBarWithNpmActivitySafe(activity); - } - - private static string TrimLastNewline(string text) { - if (string.IsNullOrEmpty(text)) { - return string.Empty; - } - - if (text.EndsWith("\r\n")) { - return text.Remove(text.Length - 2); - } - if (text.EndsWith("\r") || text.EndsWith("\n")) { - return text.Remove(text.Length - 1); - } - - return text; - } - - private void WriteNpmLogToOutputWindow(string logText) { - var pane = GetNpmOutputPane(); - if (null != pane) { - pane.WriteLine(logText); - } - - UpdateStatusBarWithNpmActivity(logText); - -#if INTEGRATE_WITH_ERROR_LIST - WriteNpmErrorsToErrorList(args); -#endif - } - - private void WriteNpmLogToOutputWindow(NpmLogEventArgs args) { - WriteNpmLogToOutputWindow(TrimLastNewline(args.LogText)); - } - - private void NpmController_CommandStarted(object sender, EventArgs e) { - StopNpmIdleTimer(); - lock (_commandCountLock) { - ++_npmCommandsExecuting; - } - } - - private void NpmController_ErrorLogged(object sender, NpmLogEventArgs e) { - WriteNpmLogToOutputWindow(e); - } - - private void NpmController_OutputLogged(object sender, NpmLogEventArgs e) { - WriteNpmLogToOutputWindow(e); - } - - private void NpmController_ExceptionLogged(object sender, NpmExceptionEventArgs e) { - WriteNpmLogToOutputWindow(ErrorHelper.GetExceptionDetailsText(e.Exception)); - } - - private void NpmController_CommandCompleted(object sender, NpmCommandCompletedEventArgs e) { - lock (_commandCountLock) { - --_npmCommandsExecuting; - if (_npmCommandsExecuting < 0) { - _npmCommandsExecuting = 0; - } - } - - string message; - if (e.WithErrors) { - message = SR.GetString( - e.Cancelled ? SR.NpmCancelledWithErrors : SR.NpmCompletedWithErrors, - e.CommandText - ); - } else if (e.Cancelled) { - message = SR.GetString(SR.NpmCancelled, e.CommandText); - } else { - message = SR.GetString(SR.NpmSuccessfullyCompleted, e.CommandText); - } - - ForceUpdateStatusBarWithNpmActivitySafe(message); - - StopNpmIdleTimer(); - _npmIdleTimer = new Timer( - _ => ProjectMgr.Site.GetUIThread().Invoke(() => _projectNode.CheckForLongPaths(e.Arguments).HandleAllExceptions(SR.ProductName).DoNotWait()), - null, 1000, Timeout.Infinite); - } - - private void StopNpmIdleTimer() { - if (null != _npmIdleTimer) { - _npmIdleTimer.Dispose(); - } - } - -#endregion - -#region Updating module hierarchy - - internal void ReloadHierarchySafe() { - NodejsPackage.Instance.GetUIThread().InvokeAsync(ReloadHierarchy) - .HandleAllExceptions(SR.ProductName) - .DoNotWait(); - } - - private void ReloadHierarchy() { - if (ProjectMgr.IsClosed) { - return; - } - - var controller = _npmController; - if (null != controller) { - if (null != RootPackage) { - var dev = controller.RootPackage.Modules.Where(package => package.IsDevDependency); - _devModulesNode.Packages = dev; - ReloadHierarchy(_devModulesNode, dev); - - var optional = controller.RootPackage.Modules.Where(package => package.IsOptionalDependency); - _optionalModulesNode.Packages = optional; - ReloadHierarchy(_optionalModulesNode, optional); - - var root = controller.RootPackage.Modules.Where(package => - package.IsDependency || - !package.IsListedInParentPackageJson); - - ReloadHierarchy(this, root); - } - - var global = controller.GlobalPackages; - if (null != global) { - _globalModulesNode.GlobalPackages = global; - ReloadHierarchy(_globalModulesNode, global.Modules); - } - - if (_firstHierarchyLoad) { - controller.FinishedRefresh += NpmController_FinishedRefresh; - _firstHierarchyLoad = false; - } - } - } - -#endregion - -#region HierarchyNode implementation - - public override int SortPriority { - get { return DefaultSortOrderNode.ReferenceContainerNode + 1; } - } - - public override string Url { - get { return NodeModulesVirtualName; } - } - - public override string Caption { - get { return _cCaption; } - } - -#endregion - -#region Command handling - - internal bool IsCurrentStateASuppressCommandsMode() { - return _suppressCommands || ProjectMgr.IsCurrentStateASuppressCommandsMode(); - } - - private void SuppressCommands() { - _suppressCommands = true; - } - - private void AllowCommands() { - _suppressCommands = false; - } - - internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { - if (cmdGroup == Guids.NodejsNpmCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidNpmInstallModules: - if (IsCurrentStateASuppressCommandsMode()) { - result = QueryStatusResult.SUPPORTED; - } else { - if (HasMissingModules) { - result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; - } else { - result = QueryStatusResult.SUPPORTED; - } - } - return VSConstants.S_OK; - - case PkgCmdId.cmdidNpmUpdateModules: - if (IsCurrentStateASuppressCommandsMode()) { - result = QueryStatusResult.SUPPORTED; - } else { - if (HasModules) { - result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; - } else { - result = QueryStatusResult.SUPPORTED; - } - } - return VSConstants.S_OK; - - case PkgCmdId.cmdidNpmInstallSingleMissingModule: - case PkgCmdId.cmdidNpmUninstallModule: - case PkgCmdId.cmdidNpmUpdateSingleModule: - case PkgCmdId.cmdidNpmOpenModuleHomepage: - result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE; - return VSConstants.S_OK; - } - } - - return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); - } - - internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { - if (cmdGroup == Guids.NodejsNpmCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidNpmInstallModules: - var t = InstallMissingModules(); - return VSConstants.S_OK; - - case PkgCmdId.cmdidNpmUpdateModules: - UpdateModules(); - return VSConstants.S_OK; - } - } - - return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); - } - - public void ManageModules(DependencyType dependencyType = DependencyType.Standard, bool isGlobal = false) { - CheckNotDisposed(); - - if (NpmController.RootPackage == null) { - NpmController.Refresh(); - if (NpmController.RootPackage == null) { - MessageBox.Show(String.Format("Unable to parse {0} from your project. Please fix any errors and try again.", NodejsConstants.PackageJsonFile)); - return; - } - } - - using (var executeVm = new NpmOutputViewModel(NpmController)) - using (var manager = new NpmPackageInstallWindow(NpmController, executeVm, dependencyType, isGlobal)) { - manager.Owner = System.Windows.Application.Current.MainWindow; - manager.ShowModal(); - } - ReloadHierarchy(); - } - - private void DoPreCommandActions() { - CheckNotDisposed(); - SuppressCommands(); - ConditionallyShowNpmOutputPane(); - } - - private bool CheckValidCommandTarget(DependencyNode node) { - if (null == node) { - return false; - } - var props = node.GetPropertiesObject(); - if (null == props || props.IsSubPackage) { - return false; - } - var package = node.Package; - if (null == package) { - return false; - } - return true; - } - - public async System.Threading.Tasks.Task InstallMissingModules() { - DoPreCommandActions(); - try { - using (var commander = NpmController.CreateNpmCommander()) { - await commander.Install(); - } - } catch (NpmNotFoundException nnfe) { - ErrorHelper.ReportNpmNotInstalled(null, nnfe); - } finally { - AllowCommands(); - } - } - - public async System.Threading.Tasks.Task InstallMissingModule(DependencyNode node) { - if (!CheckValidCommandTarget(node)) { - return; - } - - var root = _npmController.RootPackage; - if (null == root) { - return; - } - - var pkgJson = root.PackageJson; - if (null == pkgJson) { - return; - } - - var package = node.Package; - var dep = root.PackageJson.AllDependencies[package.Name]; - - DoPreCommandActions(); - try { - using (var commander = NpmController.CreateNpmCommander()) { - if (node.GetPropertiesObject().IsGlobalInstall) { - // I genuinely can't see a way this would ever happen but, just to be on the safe side... - await commander.InstallGlobalPackageByVersionAsync( - package.Name, - null == dep ? "*" : dep.VersionRangeText); - } else { - await commander.InstallPackageByVersionAsync( - package.Name, - null == dep ? "*" : dep.VersionRangeText, - DependencyType.Standard, - false); - } - } - } catch (NpmNotFoundException nnfe) { - ErrorHelper.ReportNpmNotInstalled(null, nnfe); - } finally { - AllowCommands(); - } - } - - internal async System.Threading.Tasks.Task UpdateModules(IList nodes) { - DoPreCommandActions(); - try { - using (var commander = NpmController.CreateNpmCommander()) { - if (nodes.Count == 1 && nodes[0] == this) { - await commander.UpdatePackagesAsync(); - } else { - var valid = nodes.OfType().Where(CheckValidCommandTarget).ToList(); - - var list = valid.Where(node => node.GetPropertiesObject().IsGlobalInstall).Select(node => node.Package).ToList(); - if (list.Count > 0) { - await commander.UpdateGlobalPackagesAsync(list); - } - - list = valid.Where(node => !node.GetPropertiesObject().IsGlobalInstall).Select(node => node.Package).ToList(); - if (list.Count > 0) { - await commander.UpdatePackagesAsync(list); - } - } - } - } catch (NpmNotFoundException nnfe) { - ErrorHelper.ReportNpmNotInstalled(null, nnfe); - } finally { - AllowCommands(); - } - } - - public void UpdateModules() { - var t = UpdateModules(_projectNode.GetSelectedNodes()); - } - - public async System.Threading.Tasks.Task UpdateModule(DependencyNode node) { - if (!CheckValidCommandTarget(node)) { - return; - } - DoPreCommandActions(); - try { - using (var commander = NpmController.CreateNpmCommander()) { - if (node.GetPropertiesObject().IsGlobalInstall) { - await commander.UpdateGlobalPackagesAsync(new[] { node.Package }); - } else { - await commander.UpdatePackagesAsync(new[] { node.Package }); - } - } - } catch (NpmNotFoundException nnfe) { - ErrorHelper.ReportNpmNotInstalled(null, nnfe); - } finally { - AllowCommands(); - } - } - - public async System.Threading.Tasks.Task UninstallModules() { - DoPreCommandActions(); - try { - var selected = _projectNode.GetSelectedNodes(); - using (var commander = NpmController.CreateNpmCommander()) { - foreach (var node in selected.OfType().Where(CheckValidCommandTarget)) { - if (node.GetPropertiesObject().IsGlobalInstall) { - await commander.UninstallGlobalPackageAsync(node.Package.Name); - } else { - await commander.UninstallPackageAsync(node.Package.Name); - } - } - } - } catch (NpmNotFoundException nnfe) { - ErrorHelper.ReportNpmNotInstalled(null, nnfe); - } finally { - AllowCommands(); - } - } - - public async System.Threading.Tasks.Task UninstallModule(DependencyNode node) { - if (!CheckValidCommandTarget(node)) { - return; - } - DoPreCommandActions(); - try { - using (var commander = NpmController.CreateNpmCommander()) { - if (node.GetPropertiesObject().IsGlobalInstall) { - await commander.UninstallGlobalPackageAsync(node.Package.Name); - } else { - await commander.UninstallPackageAsync(node.Package.Name); - } - } - } catch (NpmNotFoundException nnfe) { - ErrorHelper.ReportNpmNotInstalled(null, nnfe); - } finally { - AllowCommands(); - } - } - -#endregion - - public override void ManageNpmModules() { - ManageModules(); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using Microsoft.NodejsTools.Npm; +using Microsoft.NodejsTools.NpmUI; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.Project; +using MessageBox = System.Windows.MessageBox; +using Timer = System.Threading.Timer; + +namespace Microsoft.NodejsTools.Project { + internal class NodeModulesNode : AbstractNpmNode { + #region Constants + + /// + /// The caption to display for this node + /// + private const string _cCaption = "npm"; + + /// + /// The virtual name of this node. + /// + public const string NodeModulesVirtualName = "NodeModules"; + + #endregion + + #region Member variables + + private readonly GlobalModulesNode _globalModulesNode; + private readonly LocalModulesNode _devModulesNode; + private readonly LocalModulesNode _optionalModulesNode; + + private Timer _npmIdleTimer; + private INpmController _npmController; + private int _npmCommandsExecuting; + private bool _suppressCommands; + private bool _firstHierarchyLoad = true; + + private readonly object _commandCountLock = new object(); + + private bool _isDisposed; + + #endregion + + #region Initialization + + public NodeModulesNode(NodejsProjectNode root) + : base(root) { + CreateNpmController(); + + _globalModulesNode = new GlobalModulesNode(root, this); + AddChild(_globalModulesNode); + + _devModulesNode = new LocalModulesNode(root, this, "dev", "DevelopmentModules", DependencyType.Development); + AddChild(_devModulesNode); + + _optionalModulesNode = new LocalModulesNode(root, this, "optional", "OptionalModules", DependencyType.Optional); + AddChild(_optionalModulesNode); + } + + private void CheckNotDisposed() { + if (_isDisposed) { + throw new ObjectDisposedException( + "This NodeModulesNode has been disposed of and should no longer be used."); + } + } + + protected override void Dispose(bool disposing) { + if (!_isDisposed) { + _npmController.Dispose(); + + if (null != _npmIdleTimer) { + _npmIdleTimer.Dispose(); + _npmIdleTimer = null; + } + + if (null != _npmController) { + _npmController.CommandStarted -= NpmController_CommandStarted; + _npmController.OutputLogged -= NpmController_OutputLogged; + _npmController.ErrorLogged -= NpmController_ErrorLogged; + _npmController.ExceptionLogged -= NpmController_ExceptionLogged; + _npmController.CommandCompleted -= NpmController_CommandCompleted; + } + + _isDisposed = true; + } + + base.Dispose(disposing); + } + + #endregion + + #region Properties + + private string GetNpmPathFromNodePathInProject() { + var props = ProjectMgr.NodeProperties as NodejsProjectNodeProperties; + return NpmHelpers.GetPathToNpm(props != null ? props.NodeExePath : null); + } + + private class NpmPathProvider : INpmPathProvider { + private NodeModulesNode _owner; + internal NpmPathProvider(NodeModulesNode owner) { + _owner = owner; + } + + public string PathToNpm { + get { + return _owner.GetNpmPathFromNodePathInProject(); + } + } + } + + private INpmController CreateNpmController() { + if (null == _npmController) { + _npmController = NpmControllerFactory.Create( + _projectNode.ProjectHome, + NodejsPackage.Instance.NpmOptionsPage.NpmCachePath, + false, + new NpmPathProvider(this)); + _npmController.CommandStarted += NpmController_CommandStarted; + _npmController.OutputLogged += NpmController_OutputLogged; + _npmController.ErrorLogged += NpmController_ErrorLogged; + _npmController.ExceptionLogged += NpmController_ExceptionLogged; + _npmController.CommandCompleted += NpmController_CommandCompleted; + } + return _npmController; + } + + void NpmController_FinishedRefresh(object sender, EventArgs e) { + ReloadHierarchySafe(); + } + + public INpmController NpmController { + get { + return _npmController; + } + } + + internal IRootPackage RootPackage { + get { + var controller = NpmController; + return null == controller ? null : controller.RootPackage; + } + } + + private INodeModules RootModules { + get { + var root = RootPackage; + return null == root ? null : root.Modules; + } + } + + private bool HasMissingModules { + get { + var modules = RootModules; + return null != modules && modules.HasMissingModules; + } + } + + private bool HasModules { + get { + var modules = RootModules; + return null != modules && modules.Count > 0; + } + } + + #endregion + + #region Logging and status bar updates + +#if DEV14_OR_LATER + // This is the package manager pane that ships with VS2015, and we should print there if available. + private static readonly Guid VSPackageManagerPaneGuid = new Guid("C7E31C31-1451-4E05-B6BE-D11B6829E8BB"); +#else + private static readonly Guid NpmOutputPaneGuid = new Guid("25764421-33B8-4163-BD02-A94E299D52D8"); +#endif + + private OutputWindowRedirector GetNpmOutputPane() { + try { +#if DEV14_OR_LATER + return OutputWindowRedirector.Get(_projectNode.Site, VSPackageManagerPaneGuid, "Bower/npm"); +#else + return OutputWindowRedirector.Get(_projectNode.Site, NpmOutputPaneGuid, SR.GetString(SR.NpmOutputPaneTitle)); +#endif + } catch (InvalidOperationException) { + return null; + } + } + + private void ConditionallyShowNpmOutputPane() { + if (NodejsPackage.Instance.NpmOptionsPage.ShowOutputWindowWhenExecutingNpm) { + var pane = GetNpmOutputPane(); + if (null != pane) { + pane.ShowAndActivate(); + } + } + } + +#if INTEGRATE_WITH_ERROR_LIST + + private ErrorListProvider _errorListProvider; + + private ErrorListProvider GetErrorListProvider() { + if (null == _errorListProvider) { + _errorListProvider = new ErrorListProvider(_projectNode.ProjectMgr.Site); + } + return _errorListProvider; + } + + private void WriteNpmErrorsToErrorList(NpmLogEventArgs args) { + var provider = GetErrorListProvider(); + foreach (var line in args.LogText.Split(new[] {'\n' })) { + var trimmed = line.Trim(); + if (trimmed.StartsWith("npm ERR!")) { + provider.Tasks.Add(new ErrorTask() { + Category = TaskCategory.User, + ErrorCategory = TaskErrorCategory.Error, + Text = trimmed + }); + } else if (trimmed.StartsWith("npm WARN")) { + provider.Tasks.Add(new ErrorTask() { + Category = TaskCategory.User, + ErrorCategory = TaskErrorCategory.Warning, + Text = trimmed + }); + } + } + } + +#endif + + private void ForceUpdateStatusBarWithNpmActivity(string activity) { + if (string.IsNullOrEmpty(activity) || string.IsNullOrEmpty(activity.Trim())) { + return; + } + + if (!activity.Contains("npm")) { + activity = string.Format("npm: {0}", activity); + } + + var statusBar = (IVsStatusbar)_projectNode.GetService(typeof(SVsStatusbar)); + if (null != statusBar) { + statusBar.SetText(activity); + } + } + + private void ForceUpdateStatusBarWithNpmActivitySafe(string activity) { + ProjectMgr.Site.GetUIThread().InvokeAsync(() => ForceUpdateStatusBarWithNpmActivity(activity)) + .HandleAllExceptions(SR.ProductName) + .DoNotWait(); + } + + private void UpdateStatusBarWithNpmActivity(string activity) { + lock (_commandCountLock) { + if (_npmCommandsExecuting == 0) { + return; + } + } + + ForceUpdateStatusBarWithNpmActivitySafe(activity); + } + + private static string TrimLastNewline(string text) { + if (string.IsNullOrEmpty(text)) { + return string.Empty; + } + + if (text.EndsWith("\r\n")) { + return text.Remove(text.Length - 2); + } + if (text.EndsWith("\r") || text.EndsWith("\n")) { + return text.Remove(text.Length - 1); + } + + return text; + } + + private void WriteNpmLogToOutputWindow(string logText) { + var pane = GetNpmOutputPane(); + if (null != pane) { + pane.WriteLine(logText); + } + + UpdateStatusBarWithNpmActivity(logText); + +#if INTEGRATE_WITH_ERROR_LIST + WriteNpmErrorsToErrorList(args); +#endif + } + + private void WriteNpmLogToOutputWindow(NpmLogEventArgs args) { + WriteNpmLogToOutputWindow(TrimLastNewline(args.LogText)); + } + + private void NpmController_CommandStarted(object sender, EventArgs e) { + StopNpmIdleTimer(); + lock (_commandCountLock) { + ++_npmCommandsExecuting; + } + } + + private void NpmController_ErrorLogged(object sender, NpmLogEventArgs e) { + WriteNpmLogToOutputWindow(e); + } + + private void NpmController_OutputLogged(object sender, NpmLogEventArgs e) { + WriteNpmLogToOutputWindow(e); + } + + private void NpmController_ExceptionLogged(object sender, NpmExceptionEventArgs e) { + WriteNpmLogToOutputWindow(ErrorHelper.GetExceptionDetailsText(e.Exception)); + } + + private void NpmController_CommandCompleted(object sender, NpmCommandCompletedEventArgs e) { + lock (_commandCountLock) { + --_npmCommandsExecuting; + if (_npmCommandsExecuting < 0) { + _npmCommandsExecuting = 0; + } + } + + string message; + if (e.WithErrors) { + message = SR.GetString( + e.Cancelled ? SR.NpmCancelledWithErrors : SR.NpmCompletedWithErrors, + e.CommandText + ); + } else if (e.Cancelled) { + message = SR.GetString(SR.NpmCancelled, e.CommandText); + } else { + message = SR.GetString(SR.NpmSuccessfullyCompleted, e.CommandText); + } + + ForceUpdateStatusBarWithNpmActivitySafe(message); + + StopNpmIdleTimer(); + _npmIdleTimer = new Timer( + _ => ProjectMgr.Site.GetUIThread().Invoke(() => _projectNode.CheckForLongPaths(e.Arguments).HandleAllExceptions(SR.ProductName).DoNotWait()), + null, 1000, Timeout.Infinite); + } + + private void StopNpmIdleTimer() { + if (null != _npmIdleTimer) { + _npmIdleTimer.Dispose(); + } + } + +#endregion + +#region Updating module hierarchy + + internal void ReloadHierarchySafe() { + NodejsPackage.Instance.GetUIThread().InvokeAsync(ReloadHierarchy) + .HandleAllExceptions(SR.ProductName) + .DoNotWait(); + } + + private void ReloadHierarchy() { + if (ProjectMgr.IsClosed) { + return; + } + + var controller = _npmController; + if (null != controller) { + if (null != RootPackage) { + var dev = controller.RootPackage.Modules.Where(package => package.IsDevDependency); + _devModulesNode.Packages = dev; + ReloadHierarchy(_devModulesNode, dev); + + var optional = controller.RootPackage.Modules.Where(package => package.IsOptionalDependency); + _optionalModulesNode.Packages = optional; + ReloadHierarchy(_optionalModulesNode, optional); + + var root = controller.RootPackage.Modules.Where(package => + package.IsDependency || + !package.IsListedInParentPackageJson); + + ReloadHierarchy(this, root); + } + + var global = controller.GlobalPackages; + if (null != global) { + _globalModulesNode.GlobalPackages = global; + ReloadHierarchy(_globalModulesNode, global.Modules); + } + + if (_firstHierarchyLoad) { + controller.FinishedRefresh += NpmController_FinishedRefresh; + _firstHierarchyLoad = false; + } + } + } + +#endregion + +#region HierarchyNode implementation + + public override int SortPriority { + get { return DefaultSortOrderNode.ReferenceContainerNode + 1; } + } + + public override string Url { + get { return NodeModulesVirtualName; } + } + + public override string Caption { + get { return _cCaption; } + } + +#endregion + +#region Command handling + + internal bool IsCurrentStateASuppressCommandsMode() { + return _suppressCommands || ProjectMgr.IsCurrentStateASuppressCommandsMode(); + } + + private void SuppressCommands() { + _suppressCommands = true; + } + + private void AllowCommands() { + _suppressCommands = false; + } + + internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { + if (cmdGroup == Guids.NodejsNpmCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidNpmInstallModules: + if (IsCurrentStateASuppressCommandsMode()) { + result = QueryStatusResult.SUPPORTED; + } else { + if (HasMissingModules) { + result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; + } else { + result = QueryStatusResult.SUPPORTED; + } + } + return VSConstants.S_OK; + + case PkgCmdId.cmdidNpmUpdateModules: + if (IsCurrentStateASuppressCommandsMode()) { + result = QueryStatusResult.SUPPORTED; + } else { + if (HasModules) { + result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; + } else { + result = QueryStatusResult.SUPPORTED; + } + } + return VSConstants.S_OK; + + case PkgCmdId.cmdidNpmInstallSingleMissingModule: + case PkgCmdId.cmdidNpmUninstallModule: + case PkgCmdId.cmdidNpmUpdateSingleModule: + case PkgCmdId.cmdidNpmOpenModuleHomepage: + result = QueryStatusResult.SUPPORTED | QueryStatusResult.INVISIBLE; + return VSConstants.S_OK; + } + } + + return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); + } + + internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + if (cmdGroup == Guids.NodejsNpmCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidNpmInstallModules: + var t = InstallMissingModules(); + return VSConstants.S_OK; + + case PkgCmdId.cmdidNpmUpdateModules: + UpdateModules(); + return VSConstants.S_OK; + } + } + + return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); + } + + public void ManageModules(DependencyType dependencyType = DependencyType.Standard, bool isGlobal = false) { + CheckNotDisposed(); + + if (NpmController.RootPackage == null) { + NpmController.Refresh(); + if (NpmController.RootPackage == null) { + MessageBox.Show(String.Format("Unable to parse {0} from your project. Please fix any errors and try again.", NodejsConstants.PackageJsonFile)); + return; + } + } + + using (var executeVm = new NpmOutputViewModel(NpmController)) + using (var manager = new NpmPackageInstallWindow(NpmController, executeVm, dependencyType, isGlobal)) { + manager.Owner = System.Windows.Application.Current.MainWindow; + manager.ShowModal(); + } + ReloadHierarchy(); + } + + private void DoPreCommandActions() { + CheckNotDisposed(); + SuppressCommands(); + ConditionallyShowNpmOutputPane(); + } + + private bool CheckValidCommandTarget(DependencyNode node) { + if (null == node) { + return false; + } + var props = node.GetPropertiesObject(); + if (null == props || props.IsSubPackage) { + return false; + } + var package = node.Package; + if (null == package) { + return false; + } + return true; + } + + public async System.Threading.Tasks.Task InstallMissingModules() { + DoPreCommandActions(); + try { + using (var commander = NpmController.CreateNpmCommander()) { + await commander.Install(); + } + } catch (NpmNotFoundException nnfe) { + ErrorHelper.ReportNpmNotInstalled(null, nnfe); + } finally { + AllowCommands(); + } + } + + public async System.Threading.Tasks.Task InstallMissingModule(DependencyNode node) { + if (!CheckValidCommandTarget(node)) { + return; + } + + var root = _npmController.RootPackage; + if (null == root) { + return; + } + + var pkgJson = root.PackageJson; + if (null == pkgJson) { + return; + } + + var package = node.Package; + var dep = root.PackageJson.AllDependencies[package.Name]; + + DoPreCommandActions(); + try { + using (var commander = NpmController.CreateNpmCommander()) { + if (node.GetPropertiesObject().IsGlobalInstall) { + // I genuinely can't see a way this would ever happen but, just to be on the safe side... + await commander.InstallGlobalPackageByVersionAsync( + package.Name, + null == dep ? "*" : dep.VersionRangeText); + } else { + await commander.InstallPackageByVersionAsync( + package.Name, + null == dep ? "*" : dep.VersionRangeText, + DependencyType.Standard, + false); + } + } + } catch (NpmNotFoundException nnfe) { + ErrorHelper.ReportNpmNotInstalled(null, nnfe); + } finally { + AllowCommands(); + } + } + + internal async System.Threading.Tasks.Task UpdateModules(IList nodes) { + DoPreCommandActions(); + try { + using (var commander = NpmController.CreateNpmCommander()) { + if (nodes.Count == 1 && nodes[0] == this) { + await commander.UpdatePackagesAsync(); + } else { + var valid = nodes.OfType().Where(CheckValidCommandTarget).ToList(); + + var list = valid.Where(node => node.GetPropertiesObject().IsGlobalInstall).Select(node => node.Package).ToList(); + if (list.Count > 0) { + await commander.UpdateGlobalPackagesAsync(list); + } + + list = valid.Where(node => !node.GetPropertiesObject().IsGlobalInstall).Select(node => node.Package).ToList(); + if (list.Count > 0) { + await commander.UpdatePackagesAsync(list); + } + } + } + } catch (NpmNotFoundException nnfe) { + ErrorHelper.ReportNpmNotInstalled(null, nnfe); + } finally { + AllowCommands(); + } + } + + public void UpdateModules() { + var t = UpdateModules(_projectNode.GetSelectedNodes()); + } + + public async System.Threading.Tasks.Task UpdateModule(DependencyNode node) { + if (!CheckValidCommandTarget(node)) { + return; + } + DoPreCommandActions(); + try { + using (var commander = NpmController.CreateNpmCommander()) { + if (node.GetPropertiesObject().IsGlobalInstall) { + await commander.UpdateGlobalPackagesAsync(new[] { node.Package }); + } else { + await commander.UpdatePackagesAsync(new[] { node.Package }); + } + } + } catch (NpmNotFoundException nnfe) { + ErrorHelper.ReportNpmNotInstalled(null, nnfe); + } finally { + AllowCommands(); + } + } + + public async System.Threading.Tasks.Task UninstallModules() { + DoPreCommandActions(); + try { + var selected = _projectNode.GetSelectedNodes(); + using (var commander = NpmController.CreateNpmCommander()) { + foreach (var node in selected.OfType().Where(CheckValidCommandTarget)) { + if (node.GetPropertiesObject().IsGlobalInstall) { + await commander.UninstallGlobalPackageAsync(node.Package.Name); + } else { + await commander.UninstallPackageAsync(node.Package.Name); + } + } + } + } catch (NpmNotFoundException nnfe) { + ErrorHelper.ReportNpmNotInstalled(null, nnfe); + } finally { + AllowCommands(); + } + } + + public async System.Threading.Tasks.Task UninstallModule(DependencyNode node) { + if (!CheckValidCommandTarget(node)) { + return; + } + DoPreCommandActions(); + try { + using (var commander = NpmController.CreateNpmCommander()) { + if (node.GetPropertiesObject().IsGlobalInstall) { + await commander.UninstallGlobalPackageAsync(node.Package.Name); + } else { + await commander.UninstallPackageAsync(node.Package.Name); + } + } + } catch (NpmNotFoundException nnfe) { + ErrorHelper.ReportNpmNotInstalled(null, nnfe); + } finally { + AllowCommands(); + } + } + +#endregion + + public override void ManageNpmModules() { + ManageModules(); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Nodejs/Project/NodejsFileNode.cs b/Nodejs/Product/Nodejs/Project/NodejsFileNode.cs index 104ae526d..6380e5b4f 100644 --- a/Nodejs/Product/Nodejs/Project/NodejsFileNode.cs +++ b/Nodejs/Product/Nodejs/Project/NodejsFileNode.cs @@ -1,209 +1,209 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.IO; -using Microsoft.VisualStudioTools.Project; -#if DEV14_OR_LATER -using Microsoft.VisualStudio.Imaging.Interop; -using Microsoft.VisualStudio.Imaging; -#endif - -namespace Microsoft.NodejsTools.Project { - class NodejsFileNode : CommonFileNode { - private FileSystemWatcher _watcher; - - public NodejsFileNode(NodejsProjectNode root, ProjectElement e) - : base(root, e) { -#if FALSE - CreateWatcher(Url); -#endif - if (Url.Contains(AnalysisConstants.NodeModulesFolder)) { - root.EnqueueForDelayedAnalysis(this); - } else { - Analyze(); - } - } - - internal void Analyze() { - if (ProjectMgr != null && ProjectMgr.Analyzer != null && ShouldAnalyze) { - ProjectMgr.Analyzer.AnalyzeFile(Url, !IsNonMemberItem); - ProjectMgr._requireCompletionCache.Clear(); - } - ItemNode.ItemTypeChanged += ItemNode_ItemTypeChanged; - } - - internal bool ShouldAnalyze { - get { - // We analyze if we are a member item or the file is included - // Also, it should either be marked as compile or not have an item type name (value is null for node_modules - return !Url.Contains(NodejsConstants.NodeModulesStagingFolder) && - !ProjectMgr.DelayedAnalysisQueue.Contains(this) && - (!IsNonMemberItem || ProjectMgr.IncludeNodejsFile(this)) && - (ItemNode.ItemTypeName == ProjectFileConstants.Compile || string.IsNullOrEmpty(ItemNode.ItemTypeName)); - - } - } - -#if DEV14_OR_LATER - protected override ImageMoniker CodeFileIconMoniker { - get { - return KnownMonikers.JSScript; - } - } -#endif - - internal override int IncludeInProject(bool includeChildren) { - // Check if parent folder is designated as containing client-side code. - var isContent = false; - var folderNode = this.Parent as NodejsFolderNode; - if (folderNode != null) { - var contentType = folderNode.ContentType; - switch (contentType) { - case FolderContentType.Browser: - isContent = true; - break; - } - } - - var includeInProject = base.IncludeInProject(includeChildren); - - if (isContent && Url.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) { - this.ItemNode.ItemTypeName = ProjectFileConstants.Content; - } - - ProjectMgr.Analyzer.AnalyzeFile(Url, ShouldAnalyze); - - UpdateParentContentType(); - ItemNode.ItemTypeChanged += ItemNode_ItemTypeChanged; - - return includeInProject; - } - - internal override int ExcludeFromProject() { - // Analyze on removing from a project so we have the most up to date sources for this. - // Don't report errors since the file won't remain part of the project. This removes the errors from the list. - ProjectMgr.Analyzer.AnalyzeFile(Url, false); - var excludeFromProject = base.ExcludeFromProject(); - - UpdateParentContentType(); - ItemNode.ItemTypeChanged -= ItemNode_ItemTypeChanged; - - return excludeFromProject; - } - - protected override void RaiseOnItemRemoved(string documentToRemove, string[] filesToBeDeleted) { - base.RaiseOnItemRemoved(documentToRemove, filesToBeDeleted); - foreach (var file in filesToBeDeleted) { - if (!File.Exists(file)) { - ProjectMgr.Analyzer.UnloadFile(file); - } - } - } - - protected override void RenameChildNodes(FileNode parentNode) { - base.RenameChildNodes(parentNode); - this.ProjectMgr.Analyzer.ReloadComplete(); - } - - protected override NodeProperties CreatePropertiesObject() { - if (IsLinkFile) { - return new NodejsLinkFileNodeProperties(this); - } else if (IsNonMemberItem) { - return new ExcludedFileNodeProperties(this); - } - - return new NodejsIncludedFileNodeProperties(this); - } - - private void ItemNode_ItemTypeChanged(object sender, EventArgs e) { - // item type node was changed... - // if we have changed the type from compile to anything else, we should scrub - ProjectMgr.Analyzer.AnalyzeFile(Url, ShouldAnalyze); - - UpdateParentContentType(); - } - - private void UpdateParentContentType() { - var parent = this.Parent as NodejsFolderNode; - if (parent != null) { - parent.UpdateContentType(); - } - } - - private void CloseWatcher() { - if (_watcher == null) { - ProjectMgr.UnregisterFileChangeNotification(this); - } else { - _watcher.EnableRaisingEvents = false; - _watcher.Dispose(); - _watcher = null; - } - } - - // TODO: Need to update analysis for files changed outside of VS - private void CreateWatcher(string filename) { -#if FALSE - if (CommonUtils.IsSubpathOf(ProjectMgr.ProjectHome, filename)) { - // we want to subscribe to the project's file system watcher so users - // can continue to rename the directory which contains this file. - ProjectMgr.RegisterFileChangeNotification(this, FileContentsChanged); - } else { - // this is a link file which lives outside of our project directory, - // we'll need to watch the file directly, which means we're going to - // prevent it's parent directory from being renamed. - _watcher = new FileSystemWatcher(Path.GetDirectoryName(filename), Path.GetFileName(filename)); - _watcher.EnableRaisingEvents = true; - _watcher.Changed += FileContentsChanged; - _watcher.Renamed += FileContentsChanged; - _watcher.NotifyFilter = NotifyFilters.LastWrite; - } -#endif - } - - internal override void RenameInStorage(string oldName, string newName) { - CloseWatcher(); - bool renamed = false; - try { - base.RenameInStorage(oldName, newName); - renamed = true; - CreateWatcher(newName); - } finally { - if (!renamed) { - CreateWatcher(oldName); - } - } - } - - public new NodejsProjectNode ProjectMgr { - get { - return (NodejsProjectNode)base.ProjectMgr; - } - } - - public override void Remove(bool removeFromStorage) { - ItemNode.ItemTypeChanged -= ItemNode_ItemTypeChanged; - base.Remove(removeFromStorage); - CloseWatcher(); - } - - public override void Close() { - ItemNode.ItemTypeChanged -= ItemNode_ItemTypeChanged; - base.Close(); - CloseWatcher(); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.IO; +using Microsoft.VisualStudioTools.Project; +#if DEV14_OR_LATER +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Imaging; +#endif + +namespace Microsoft.NodejsTools.Project { + class NodejsFileNode : CommonFileNode { + private FileSystemWatcher _watcher; + + public NodejsFileNode(NodejsProjectNode root, ProjectElement e) + : base(root, e) { +#if FALSE + CreateWatcher(Url); +#endif + if (Url.Contains(AnalysisConstants.NodeModulesFolder)) { + root.EnqueueForDelayedAnalysis(this); + } else { + Analyze(); + } + } + + internal void Analyze() { + if (ProjectMgr != null && ProjectMgr.Analyzer != null && ShouldAnalyze) { + ProjectMgr.Analyzer.AnalyzeFile(Url, !IsNonMemberItem); + ProjectMgr._requireCompletionCache.Clear(); + } + ItemNode.ItemTypeChanged += ItemNode_ItemTypeChanged; + } + + internal bool ShouldAnalyze { + get { + // We analyze if we are a member item or the file is included + // Also, it should either be marked as compile or not have an item type name (value is null for node_modules + return !Url.Contains(NodejsConstants.NodeModulesStagingFolder) && + !ProjectMgr.DelayedAnalysisQueue.Contains(this) && + (!IsNonMemberItem || ProjectMgr.IncludeNodejsFile(this)) && + (ItemNode.ItemTypeName == ProjectFileConstants.Compile || string.IsNullOrEmpty(ItemNode.ItemTypeName)); + + } + } + +#if DEV14_OR_LATER + protected override ImageMoniker CodeFileIconMoniker { + get { + return KnownMonikers.JSScript; + } + } +#endif + + internal override int IncludeInProject(bool includeChildren) { + // Check if parent folder is designated as containing client-side code. + var isContent = false; + var folderNode = this.Parent as NodejsFolderNode; + if (folderNode != null) { + var contentType = folderNode.ContentType; + switch (contentType) { + case FolderContentType.Browser: + isContent = true; + break; + } + } + + var includeInProject = base.IncludeInProject(includeChildren); + + if (isContent && Url.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) { + this.ItemNode.ItemTypeName = ProjectFileConstants.Content; + } + + ProjectMgr.Analyzer.AnalyzeFile(Url, ShouldAnalyze); + + UpdateParentContentType(); + ItemNode.ItemTypeChanged += ItemNode_ItemTypeChanged; + + return includeInProject; + } + + internal override int ExcludeFromProject() { + // Analyze on removing from a project so we have the most up to date sources for this. + // Don't report errors since the file won't remain part of the project. This removes the errors from the list. + ProjectMgr.Analyzer.AnalyzeFile(Url, false); + var excludeFromProject = base.ExcludeFromProject(); + + UpdateParentContentType(); + ItemNode.ItemTypeChanged -= ItemNode_ItemTypeChanged; + + return excludeFromProject; + } + + protected override void RaiseOnItemRemoved(string documentToRemove, string[] filesToBeDeleted) { + base.RaiseOnItemRemoved(documentToRemove, filesToBeDeleted); + foreach (var file in filesToBeDeleted) { + if (!File.Exists(file)) { + ProjectMgr.Analyzer.UnloadFile(file); + } + } + } + + protected override void RenameChildNodes(FileNode parentNode) { + base.RenameChildNodes(parentNode); + this.ProjectMgr.Analyzer.ReloadComplete(); + } + + protected override NodeProperties CreatePropertiesObject() { + if (IsLinkFile) { + return new NodejsLinkFileNodeProperties(this); + } else if (IsNonMemberItem) { + return new ExcludedFileNodeProperties(this); + } + + return new NodejsIncludedFileNodeProperties(this); + } + + private void ItemNode_ItemTypeChanged(object sender, EventArgs e) { + // item type node was changed... + // if we have changed the type from compile to anything else, we should scrub + ProjectMgr.Analyzer.AnalyzeFile(Url, ShouldAnalyze); + + UpdateParentContentType(); + } + + private void UpdateParentContentType() { + var parent = this.Parent as NodejsFolderNode; + if (parent != null) { + parent.UpdateContentType(); + } + } + + private void CloseWatcher() { + if (_watcher == null) { + ProjectMgr.UnregisterFileChangeNotification(this); + } else { + _watcher.EnableRaisingEvents = false; + _watcher.Dispose(); + _watcher = null; + } + } + + // TODO: Need to update analysis for files changed outside of VS + private void CreateWatcher(string filename) { +#if FALSE + if (CommonUtils.IsSubpathOf(ProjectMgr.ProjectHome, filename)) { + // we want to subscribe to the project's file system watcher so users + // can continue to rename the directory which contains this file. + ProjectMgr.RegisterFileChangeNotification(this, FileContentsChanged); + } else { + // this is a link file which lives outside of our project directory, + // we'll need to watch the file directly, which means we're going to + // prevent it's parent directory from being renamed. + _watcher = new FileSystemWatcher(Path.GetDirectoryName(filename), Path.GetFileName(filename)); + _watcher.EnableRaisingEvents = true; + _watcher.Changed += FileContentsChanged; + _watcher.Renamed += FileContentsChanged; + _watcher.NotifyFilter = NotifyFilters.LastWrite; + } +#endif + } + + internal override void RenameInStorage(string oldName, string newName) { + CloseWatcher(); + bool renamed = false; + try { + base.RenameInStorage(oldName, newName); + renamed = true; + CreateWatcher(newName); + } finally { + if (!renamed) { + CreateWatcher(oldName); + } + } + } + + public new NodejsProjectNode ProjectMgr { + get { + return (NodejsProjectNode)base.ProjectMgr; + } + } + + public override void Remove(bool removeFromStorage) { + ItemNode.ItemTypeChanged -= ItemNode_ItemTypeChanged; + base.Remove(removeFromStorage); + CloseWatcher(); + } + + public override void Close() { + ItemNode.ItemTypeChanged -= ItemNode_ItemTypeChanged; + base.Close(); + CloseWatcher(); + } + } +} diff --git a/Nodejs/Product/Nodejs/Project/NodejsFolderNode.cs b/Nodejs/Product/Nodejs/Project/NodejsFolderNode.cs index da3e1a83d..fda25299b 100644 --- a/Nodejs/Product/Nodejs/Project/NodejsFolderNode.cs +++ b/Nodejs/Product/Nodejs/Project/NodejsFolderNode.cs @@ -1,250 +1,250 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.IO; -using System.Text; -using Microsoft.NodejsTools.Options; -using Microsoft.VisualStudio; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.Project; -using VSLangProj; - -namespace Microsoft.NodejsTools.Project { - class NodejsFolderNode : CommonFolderNode { - private readonly CommonProjectNode _project; - private FolderContentType _contentType = FolderContentType.NotAssigned; - private bool _containsNodeOrBrowserFiles = false; - - public NodejsFolderNode(CommonProjectNode root, ProjectElement element) : base(root, element) { - _project = root; - } - - public FolderContentType ContentType { - get { - if (_contentType == FolderContentType.NotAssigned) { - UpdateContentType(); - } - - return _contentType; - } - } - - public void UpdateContentType() { - var oldContentType = _contentType; - _contentType = FolderContentType.None; - var parent = Parent as NodejsFolderNode; - _containsNodeOrBrowserFiles = false; - - if (ItemNode.IsExcluded || ItemNode.Url.Contains(NodejsConstants.NodeModulesFolder)) { - _contentType = FolderContentType.None; - } else { - // Iterate through all of the javascript files in a directory to determine whether - // the build actions are Content, Compile, or a mix of the two. - var nodejsFileNodes = EnumNodesOfType(); - FolderContentType contentType = FolderContentType.None; - foreach (var fileNode in nodejsFileNodes) { - if (!fileNode.Url.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) { - continue; - } - - var properties = fileNode.NodeProperties as IncludedFileNodeProperties; - if (properties != null) { - _containsNodeOrBrowserFiles = true; - switch (properties.BuildAction) { - case prjBuildAction.prjBuildActionContent: - contentType |= FolderContentType.Browser; - break; - case prjBuildAction.prjBuildActionCompile: - contentType |= FolderContentType.Node; - break; - } - - if (contentType == FolderContentType.Mixed) { - break; - } - } - } - - // If there are no relevant javascript files in the folder, then fall back to - // the parent type. This enables us to provide good defaults in the event that - // an item is added to the directory later. - if (contentType == FolderContentType.None) { - // Set as parent content type - if (parent != null) { - contentType = parent.ContentType; - } - } - - _contentType = contentType; - ProjectMgr.ReDrawNode(this, UIHierarchyElement.Caption); - } - - // Update the caption of the parent folder accordingly - if (parent != null && _contentType != oldContentType) { - parent.UpdateContentType(); - } - } - - public override string Caption { - get { - var res = base.Caption; - - if (NodejsPackage.Instance.GeneralOptionsPage.ShowBrowserAndNodeLabels && - NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevel != AnalysisLevel.Preview && - _containsNodeOrBrowserFiles) { - res = AppendLabel(res, ContentType); - } - return res; - } - } - - public override void RemoveChild(HierarchyNode node) { - base.RemoveChild(node); - UpdateContentType(); - } - - public override void AddChild(HierarchyNode node) { - base.AddChild(node); - - // If we are adding an immediate child to a directory, then set the content type - // acording to the content type of the folder it is being moved to. - var nodejsFileNode = node as NodejsFileNode; - if (nodejsFileNode != null && nodejsFileNode.Url.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && nodejsFileNode.Parent == this) { - var properties = nodejsFileNode.NodeProperties as IncludedFileNodeProperties; - if (properties != null) { - switch (ContentType) { - case FolderContentType.Browser: - properties.ItemType = ProjectFileConstants.Content; - break; - case FolderContentType.Node: - properties.ItemType = ProjectFileConstants.Compile; - break; - } - } - } - - UpdateContentType(); - } - - /// - /// Append a label denoting browser-side code, node, or both depending on the content type - /// - /// - /// - /// - /// - public static string AppendLabel(string folderName, FolderContentType contentType) { - switch (contentType) { - case FolderContentType.Browser: - folderName += " (browser)"; - break; - case FolderContentType.Node: - folderName += " (node)"; - break; - case FolderContentType.Mixed: - folderName += " (node, browser)"; - break; - } - return folderName; - } - - internal override int IncludeInProject(bool includeChildren) { - // Include node_modules folder is generally unecessary and can cause VS to hang. - // http://nodejstools.codeplex.com/workitem/1432 - // Check if the folder is node_modules, and warn the user to ensure they don't run into this issue or at least set expectations appropriately. - string nodeModulesPath = Path.Combine(_project.FullPathToChildren, "node_modules"); - if (CommonUtils.IsSameDirectory(nodeModulesPath, ItemNode.Url) && - !ShouldIncludeNodeModulesFolderInProject()) { - return VSConstants.S_OK; - } - return base.IncludeInProject(includeChildren); - } - - internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { - if (cmdGroup == Guids.NodejsCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidSetAsContent: - if (_containsNodeOrBrowserFiles && ContentType.HasFlag(FolderContentType.Node)) { - result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; - } - return VSConstants.S_OK; - case PkgCmdId.cmdidSetAsCompile: - if (_containsNodeOrBrowserFiles && ContentType.HasFlag(FolderContentType.Browser)) { - result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; - } - return VSConstants.S_OK; - } - } - return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); - } - - internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { - if (cmdGroup == Guids.NodejsCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidSetAsContent: - SetItemTypeRecursively(prjBuildAction.prjBuildActionContent); - return VSConstants.S_OK; - case PkgCmdId.cmdidSetAsCompile: - SetItemTypeRecursively(prjBuildAction.prjBuildActionCompile); - return VSConstants.S_OK; - } - } - return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); - } - - internal void SetItemTypeRecursively(prjBuildAction buildAction) { - var fileNodesEnumerator = this.EnumNodesOfType().GetEnumerator(); - while (fileNodesEnumerator.MoveNext()) { - var includedFileNodeProperties = fileNodesEnumerator.Current.NodeProperties as IncludedFileNodeProperties; - if (includedFileNodeProperties != null && includedFileNodeProperties.URL.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) { - includedFileNodeProperties.BuildAction = buildAction; - } - } - } - - private bool ShouldIncludeNodeModulesFolderInProject() { - var includeNodeModulesButton = new TaskDialogButton(SR.GetString(SR.IncludeNodeModulesIncludeTitle), SR.GetString(SR.IncludeNodeModulesIncludeDescription)); - var cancelOperationButton = new TaskDialogButton(SR.GetString(SR.IncludeNodeModulesCancelTitle)); - var taskDialog = new TaskDialog(_project.ProjectMgr.Site) { - AllowCancellation = true, - EnableHyperlinks = true, - Title = SR.ProductName, - MainIcon = TaskDialogIcon.Warning, - Content = SR.GetString(SR.IncludeNodeModulesContent), - Buttons = { - cancelOperationButton, - includeNodeModulesButton - }, - FooterIcon = TaskDialogIcon.Information, - Footer = SR.GetString(SR.IncludeNodeModulesInformation), - SelectedButton = cancelOperationButton - }; - - var button = taskDialog.ShowModal(); - - return button == includeNodeModulesButton; - } - } - - internal enum FolderContentType { - None = 0, - Browser = 1, - Node = 2, - Mixed = 3, - NotAssigned - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.IO; +using System.Text; +using Microsoft.NodejsTools.Options; +using Microsoft.VisualStudio; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.Project; +using VSLangProj; + +namespace Microsoft.NodejsTools.Project { + class NodejsFolderNode : CommonFolderNode { + private readonly CommonProjectNode _project; + private FolderContentType _contentType = FolderContentType.NotAssigned; + private bool _containsNodeOrBrowserFiles = false; + + public NodejsFolderNode(CommonProjectNode root, ProjectElement element) : base(root, element) { + _project = root; + } + + public FolderContentType ContentType { + get { + if (_contentType == FolderContentType.NotAssigned) { + UpdateContentType(); + } + + return _contentType; + } + } + + public void UpdateContentType() { + var oldContentType = _contentType; + _contentType = FolderContentType.None; + var parent = Parent as NodejsFolderNode; + _containsNodeOrBrowserFiles = false; + + if (ItemNode.IsExcluded || ItemNode.Url.Contains(NodejsConstants.NodeModulesFolder)) { + _contentType = FolderContentType.None; + } else { + // Iterate through all of the javascript files in a directory to determine whether + // the build actions are Content, Compile, or a mix of the two. + var nodejsFileNodes = EnumNodesOfType(); + FolderContentType contentType = FolderContentType.None; + foreach (var fileNode in nodejsFileNodes) { + if (!fileNode.Url.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) { + continue; + } + + var properties = fileNode.NodeProperties as IncludedFileNodeProperties; + if (properties != null) { + _containsNodeOrBrowserFiles = true; + switch (properties.BuildAction) { + case prjBuildAction.prjBuildActionContent: + contentType |= FolderContentType.Browser; + break; + case prjBuildAction.prjBuildActionCompile: + contentType |= FolderContentType.Node; + break; + } + + if (contentType == FolderContentType.Mixed) { + break; + } + } + } + + // If there are no relevant javascript files in the folder, then fall back to + // the parent type. This enables us to provide good defaults in the event that + // an item is added to the directory later. + if (contentType == FolderContentType.None) { + // Set as parent content type + if (parent != null) { + contentType = parent.ContentType; + } + } + + _contentType = contentType; + ProjectMgr.ReDrawNode(this, UIHierarchyElement.Caption); + } + + // Update the caption of the parent folder accordingly + if (parent != null && _contentType != oldContentType) { + parent.UpdateContentType(); + } + } + + public override string Caption { + get { + var res = base.Caption; + + if (NodejsPackage.Instance.GeneralOptionsPage.ShowBrowserAndNodeLabels && + NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevel != AnalysisLevel.Preview && + _containsNodeOrBrowserFiles) { + res = AppendLabel(res, ContentType); + } + return res; + } + } + + public override void RemoveChild(HierarchyNode node) { + base.RemoveChild(node); + UpdateContentType(); + } + + public override void AddChild(HierarchyNode node) { + base.AddChild(node); + + // If we are adding an immediate child to a directory, then set the content type + // acording to the content type of the folder it is being moved to. + var nodejsFileNode = node as NodejsFileNode; + if (nodejsFileNode != null && nodejsFileNode.Url.EndsWith(".js", StringComparison.OrdinalIgnoreCase) && nodejsFileNode.Parent == this) { + var properties = nodejsFileNode.NodeProperties as IncludedFileNodeProperties; + if (properties != null) { + switch (ContentType) { + case FolderContentType.Browser: + properties.ItemType = ProjectFileConstants.Content; + break; + case FolderContentType.Node: + properties.ItemType = ProjectFileConstants.Compile; + break; + } + } + } + + UpdateContentType(); + } + + /// + /// Append a label denoting browser-side code, node, or both depending on the content type + /// + /// + /// + /// + /// + public static string AppendLabel(string folderName, FolderContentType contentType) { + switch (contentType) { + case FolderContentType.Browser: + folderName += " (browser)"; + break; + case FolderContentType.Node: + folderName += " (node)"; + break; + case FolderContentType.Mixed: + folderName += " (node, browser)"; + break; + } + return folderName; + } + + internal override int IncludeInProject(bool includeChildren) { + // Include node_modules folder is generally unecessary and can cause VS to hang. + // http://nodejstools.codeplex.com/workitem/1432 + // Check if the folder is node_modules, and warn the user to ensure they don't run into this issue or at least set expectations appropriately. + string nodeModulesPath = Path.Combine(_project.FullPathToChildren, "node_modules"); + if (CommonUtils.IsSameDirectory(nodeModulesPath, ItemNode.Url) && + !ShouldIncludeNodeModulesFolderInProject()) { + return VSConstants.S_OK; + } + return base.IncludeInProject(includeChildren); + } + + internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { + if (cmdGroup == Guids.NodejsCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidSetAsContent: + if (_containsNodeOrBrowserFiles && ContentType.HasFlag(FolderContentType.Node)) { + result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; + } + return VSConstants.S_OK; + case PkgCmdId.cmdidSetAsCompile: + if (_containsNodeOrBrowserFiles && ContentType.HasFlag(FolderContentType.Browser)) { + result = QueryStatusResult.ENABLED | QueryStatusResult.SUPPORTED; + } + return VSConstants.S_OK; + } + } + return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); + } + + internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + if (cmdGroup == Guids.NodejsCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidSetAsContent: + SetItemTypeRecursively(prjBuildAction.prjBuildActionContent); + return VSConstants.S_OK; + case PkgCmdId.cmdidSetAsCompile: + SetItemTypeRecursively(prjBuildAction.prjBuildActionCompile); + return VSConstants.S_OK; + } + } + return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); + } + + internal void SetItemTypeRecursively(prjBuildAction buildAction) { + var fileNodesEnumerator = this.EnumNodesOfType().GetEnumerator(); + while (fileNodesEnumerator.MoveNext()) { + var includedFileNodeProperties = fileNodesEnumerator.Current.NodeProperties as IncludedFileNodeProperties; + if (includedFileNodeProperties != null && includedFileNodeProperties.URL.EndsWith(".js", StringComparison.OrdinalIgnoreCase)) { + includedFileNodeProperties.BuildAction = buildAction; + } + } + } + + private bool ShouldIncludeNodeModulesFolderInProject() { + var includeNodeModulesButton = new TaskDialogButton(SR.GetString(SR.IncludeNodeModulesIncludeTitle), SR.GetString(SR.IncludeNodeModulesIncludeDescription)); + var cancelOperationButton = new TaskDialogButton(SR.GetString(SR.IncludeNodeModulesCancelTitle)); + var taskDialog = new TaskDialog(_project.ProjectMgr.Site) { + AllowCancellation = true, + EnableHyperlinks = true, + Title = SR.ProductName, + MainIcon = TaskDialogIcon.Warning, + Content = SR.GetString(SR.IncludeNodeModulesContent), + Buttons = { + cancelOperationButton, + includeNodeModulesButton + }, + FooterIcon = TaskDialogIcon.Information, + Footer = SR.GetString(SR.IncludeNodeModulesInformation), + SelectedButton = cancelOperationButton + }; + + var button = taskDialog.ShowModal(); + + return button == includeNodeModulesButton; + } + } + + internal enum FolderContentType { + None = 0, + Browser = 1, + Node = 2, + Mixed = 3, + NotAssigned + } +} diff --git a/Nodejs/Product/Nodejs/Project/NodejsGeneralPropertyPageControl.cs b/Nodejs/Product/Nodejs/Project/NodejsGeneralPropertyPageControl.cs index 44d1c2098..3d646f5f1 100644 --- a/Nodejs/Product/Nodejs/Project/NodejsGeneralPropertyPageControl.cs +++ b/Nodejs/Product/Nodejs/Project/NodejsGeneralPropertyPageControl.cs @@ -23,7 +23,7 @@ using System.Windows.Forms; using Microsoft.VisualStudioTools; using Microsoft.VisualStudioTools.Project; - + namespace Microsoft.NodejsTools.Project { partial class NodejsGeneralPropertyPageControl : UserControl { private readonly NodejsGeneralPropertyPage _propPage; diff --git a/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs b/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs index c7ef49642..e2186f36c 100644 --- a/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs +++ b/Nodejs/Product/Nodejs/Project/NodejsProjectLauncher.cs @@ -1,438 +1,438 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.NetworkInformation; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Web; -using System.Windows.Forms; -using Microsoft.NodejsTools.Debugger; -using Microsoft.NodejsTools.Debugger.DebugEngine; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.Project; -using Microsoft.NodejsTools.TypeScript; - -namespace Microsoft.NodejsTools.Project { - class NodejsProjectLauncher : IProjectLauncher { - private readonly NodejsProjectNode _project; - private int? _testServerPort; - - public NodejsProjectLauncher(NodejsProjectNode project) { - _project = project; - - var portNumber = _project.GetProjectProperty(NodejsConstants.NodejsPort); - int portNum; - if (Int32.TryParse(portNumber, out portNum)) { - _testServerPort = portNum; - } - } - - #region IProjectLauncher Members - - public int LaunchProject(bool debug) { - NodejsPackage.Instance.Logger.LogEvent(Logging.NodejsToolsLogEvent.Launch, debug ? 1 : 0); - return Start(ResolveStartupFile(), debug); - } - - public int LaunchFile(string file, bool debug) { - NodejsPackage.Instance.Logger.LogEvent(Logging.NodejsToolsLogEvent.Launch, debug ? 1 : 0); - return Start(file, debug); - } - - private int Start(string file, bool debug) { - string nodePath = GetNodePath(); - if (nodePath == null) { - Nodejs.ShowNodejsNotInstalled(); - return VSConstants.S_OK; - } - - bool startBrowser = ShouldStartBrowser(); - - if (debug) { - StartWithDebugger(file); - } else { - var psi = new ProcessStartInfo(); - psi.UseShellExecute = false; - - psi.FileName = nodePath; - psi.Arguments = GetFullArguments(file); - psi.WorkingDirectory = _project.GetWorkingDirectory(); - - string webBrowserUrl = GetFullUrl(); - Uri uri = null; - if (!String.IsNullOrWhiteSpace(webBrowserUrl)) { - uri = new Uri(webBrowserUrl); - - psi.EnvironmentVariables["PORT"] = uri.Port.ToString(); - } - - foreach (var nameValue in GetEnvironmentVariables()) { - psi.EnvironmentVariables[nameValue.Key] = nameValue.Value; - } - - var process = NodeProcess.Start( - psi, - NodejsPackage.Instance.GeneralOptionsPage.WaitOnAbnormalExit, - NodejsPackage.Instance.GeneralOptionsPage.WaitOnNormalExit - ); - - if (startBrowser && uri != null) { - OnPortOpenedHandler.CreateHandler( - uri.Port, - shortCircuitPredicate: () => process.HasExited, - action: () => { - VsShellUtilities.OpenBrowser(webBrowserUrl, (uint)__VSOSPFLAGS.OSP_LaunchNewBrowser); - } - ); - } - } - return VSConstants.S_OK; - } - - private string GetFullArguments(string file, bool includeNodeArgs = true) { - string res = String.Empty; - if (includeNodeArgs) { - var nodeArgs = _project.GetProjectProperty(NodejsConstants.NodeExeArguments); - if (!String.IsNullOrWhiteSpace(nodeArgs)) { - res = nodeArgs + " "; - } - } - res += "\"" + file + "\""; - var scriptArgs = _project.GetProjectProperty(NodejsConstants.ScriptArguments); - if (!String.IsNullOrWhiteSpace(scriptArgs)) { - res += " " + scriptArgs; - } - return res; - } - - private string GetNodePath() { - var overridePath = _project.GetProjectProperty(NodejsConstants.NodeExePath); - return Nodejs.GetAbsoluteNodeExePath(_project.ProjectHome, overridePath); - } - - #endregion - - private string GetFullUrl() { - var host = _project.GetProjectProperty(NodejsConstants.LaunchUrl); - - try { - return GetFullUrl(host, TestServerPort); - } catch (UriFormatException) { - var output = OutputWindowRedirector.GetGeneral(NodejsPackage.Instance); - output.WriteErrorLine(SR.GetString(SR.ErrorInvalidLaunchUrl, host)); - output.ShowAndActivate(); - return string.Empty; - } - } - - internal static string GetFullUrl(string host, int port) { - UriBuilder builder; - Uri uri; - if (Uri.TryCreate(host, UriKind.Absolute, out uri)) { - builder = new UriBuilder(uri); - } else { - builder = new UriBuilder(); - builder.Scheme = Uri.UriSchemeHttp; - builder.Host = "localhost"; - builder.Path = host; - } - - builder.Port = port; - - return builder.ToString(); - } - - private string TestServerPortString { - get { - if (!_testServerPort.HasValue) { - _testServerPort = GetFreePort(); - } - return _testServerPort.Value.ToString(CultureInfo.InvariantCulture); - } - } - - private int TestServerPort { - get { - if (!_testServerPort.HasValue) { - _testServerPort = GetFreePort(); - } - return _testServerPort.Value; - } - } - - /// - /// Default implementation of the "Start Debugging" command. - /// - private void StartWithDebugger(string startupFile) { - VsDebugTargetInfo dbgInfo = new VsDebugTargetInfo(); - dbgInfo.cbSize = (uint)Marshal.SizeOf(dbgInfo); - - if (SetupDebugInfo(ref dbgInfo, startupFile)) { - LaunchDebugger(_project.Site, dbgInfo); - } - } - - - private void LaunchDebugger(IServiceProvider provider, VsDebugTargetInfo dbgInfo) { - if (!Directory.Exists(dbgInfo.bstrCurDir)) { - MessageBox.Show(String.Format("Working directory \"{0}\" does not exist.", dbgInfo.bstrCurDir), "Node.js Tools for Visual Studio"); - } else if (!File.Exists(dbgInfo.bstrExe)) { - MessageBox.Show(String.Format("Interpreter \"{0}\" does not exist.", dbgInfo.bstrExe), "Node.js Tools for Visual Studio"); - } else if (DoesProjectSupportDebugging()) { - VsShellUtilities.LaunchDebugger(provider, dbgInfo); - } - } - - private bool DoesProjectSupportDebugging() { - var typeScriptOutFile = _project.GetProjectProperty("TypeScriptOutFile"); - if (!string.IsNullOrEmpty(typeScriptOutFile)) { - return MessageBox.Show( - "This TypeScript project has 'Combine Javascript output into file' option enabled. This option is not supported by NTVS debugger, " + - "and may result in erratic behavior of breakpoints, stepping, and debug tool windows. Are you sure you want to start debugging?", - SR.ProductName, - MessageBoxButtons.YesNo, - MessageBoxIcon.Warning - ) == DialogResult.Yes; - } - - return true; - } - - private void AppendOption(ref VsDebugTargetInfo dbgInfo, string option, string value) { - if (!String.IsNullOrWhiteSpace(dbgInfo.bstrOptions)) { - dbgInfo.bstrOptions += ";"; - } - - dbgInfo.bstrOptions += option + "=" + HttpUtility.UrlEncode(value); - } - - /// - /// Sets up debugger information. - /// - private bool SetupDebugInfo(ref VsDebugTargetInfo dbgInfo, string startupFile) { - dbgInfo.dlo = DEBUG_LAUNCH_OPERATION.DLO_CreateProcess; - - dbgInfo.bstrExe = GetNodePath(); - dbgInfo.bstrCurDir = _project.GetWorkingDirectory(); - dbgInfo.bstrArg = GetFullArguments(startupFile, includeNodeArgs: false); // we need to supply node args via options - dbgInfo.bstrRemoteMachine = null; - var nodeArgs = _project.GetProjectProperty(NodejsConstants.NodeExeArguments); - if (!String.IsNullOrWhiteSpace(nodeArgs)) { - AppendOption(ref dbgInfo, AD7Engine.InterpreterOptions, nodeArgs); - } - - var url = GetFullUrl(); - if (ShouldStartBrowser() && !String.IsNullOrWhiteSpace(url)) { - AppendOption(ref dbgInfo, AD7Engine.WebBrowserUrl, url); - } - - var debuggerPort = _project.GetProjectProperty(NodejsConstants.DebuggerPort); - if (!String.IsNullOrWhiteSpace(debuggerPort)) { - AppendOption(ref dbgInfo, AD7Engine.DebuggerPort, debuggerPort); - } - - if (NodejsPackage.Instance.GeneralOptionsPage.WaitOnAbnormalExit) { - AppendOption(ref dbgInfo, AD7Engine.WaitOnAbnormalExitSetting, "true"); - } - - if (NodejsPackage.Instance.GeneralOptionsPage.WaitOnNormalExit) { - AppendOption(ref dbgInfo, AD7Engine.WaitOnNormalExitSetting, "true"); - } - - dbgInfo.fSendStdoutToOutputWindow = 0; - - StringDictionary env = new StringDictionary(); - if (!String.IsNullOrWhiteSpace(url)) { - Uri webUrl = new Uri(url); - env["PORT"] = webUrl.Port.ToString(); - } - - foreach (var nameValue in GetEnvironmentVariables()) { - env[nameValue.Key] = nameValue.Value; - } - - if (env.Count > 0) { - // add any inherited env vars - var variables = Environment.GetEnvironmentVariables(); - foreach (var key in variables.Keys) { - string strKey = (string)key; - if (!env.ContainsKey(strKey)) { - env.Add(strKey, (string)variables[key]); - } - } - - //Environemnt variables should be passed as a - //null-terminated block of null-terminated strings. - //Each string is in the following form:name=value\0 - StringBuilder buf = new StringBuilder(); - foreach (DictionaryEntry entry in env) { - buf.AppendFormat("{0}={1}\0", entry.Key, entry.Value); - } - buf.Append("\0"); - dbgInfo.bstrEnv = buf.ToString(); - } - - // Set the Node debugger - dbgInfo.clsidCustom = AD7Engine.DebugEngineGuid; - dbgInfo.grfLaunch = (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_StopDebuggingOnEnd; - return true; - } - - private bool ShouldStartBrowser() { - var startBrowser = _project.GetProjectProperty(NodejsConstants.StartWebBrowser); - bool fStartBrowser; - if (!String.IsNullOrEmpty(startBrowser) && - Boolean.TryParse(startBrowser, out fStartBrowser)) { - return fStartBrowser; - } - - return true; - } - - private IEnumerable> GetEnvironmentVariables() { - var envVars = _project.GetProjectProperty(NodejsConstants.Environment); - if (envVars != null) { - foreach (var envVar in envVars.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) { - var nameValue = envVar.Split(new[] { '=' }, 2); - if (nameValue.Length == 2) { - yield return new KeyValuePair(nameValue[0], nameValue[1]); - } - } - } - } - - private static int GetFreePort() { - return Enumerable.Range(new Random().Next(1200, 2000), 60000).Except( - from connection in IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections() - select connection.LocalEndPoint.Port - ).First(); - } - - private string ResolveStartupFile() { - string startupFile = _project.GetStartupFile(); - if (string.IsNullOrEmpty(startupFile)) { - throw new ApplicationException("Please select a startup file to launch by right-clicking the file in Solution Explorer and selecting 'Set as Node.js Startup File' or by modifying your configuration in project properties."); - } - - if (TypeScriptHelpers.IsTypeScriptFile(startupFile)) { - startupFile = TypeScriptHelpers.GetTypeScriptBackedJavaScriptFile(_project, startupFile); - } - return startupFile; - } - } - - internal class OnPortOpenedHandler { - - class OnPortOpenedInfo { - public readonly int Port; - public readonly TimeSpan? Timeout; - public readonly int Sleep; - public readonly Func ShortCircuitPredicate; - public readonly Action Action; - public readonly DateTime StartTime; - - public OnPortOpenedInfo( - int port, - int? timeout = null, - int? sleep = null, - Func shortCircuitPredicate = null, - Action action = null - ) { - Port = port; - if (timeout.HasValue) { - Timeout = TimeSpan.FromMilliseconds(Convert.ToDouble(timeout)); - } - Sleep = sleep ?? 500; // 1/2 second sleep - ShortCircuitPredicate = shortCircuitPredicate ?? (() => false); - Action = action ?? (() => { }); - StartTime = System.DateTime.Now; - } - } - - internal static void CreateHandler( - int port, - int? timeout = null, - int? sleep = null, - Func shortCircuitPredicate = null, - Action action = null - ) { - ThreadPool.QueueUserWorkItem( - OnPortOpened, - new OnPortOpenedInfo( - port, - timeout, - sleep, - shortCircuitPredicate, - action - ) - ); - } - - private static void OnPortOpened(object infoObj) { - var info = (OnPortOpenedInfo)infoObj; - - using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { - socket.Blocking = true; - try { - while (true) { - // Short circuit - if (info.ShortCircuitPredicate()) { - return; - } - - // Try connect - try { - socket.Connect(IPAddress.Loopback, info.Port); - break; - } catch { - // Connect failure - // Fall through - } - - // Timeout - if (info.Timeout.HasValue && (System.DateTime.Now - info.StartTime) >= info.Timeout) { - break; - } - - // Sleep - System.Threading.Thread.Sleep(info.Sleep); - } - } finally { - socket.Close(); - } - } - - // Launch browser (if not short-circuited) - if (!info.ShortCircuitPredicate()) { - info.Action(); - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Web; +using System.Windows.Forms; +using Microsoft.NodejsTools.Debugger; +using Microsoft.NodejsTools.Debugger.DebugEngine; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.Project; +using Microsoft.NodejsTools.TypeScript; + +namespace Microsoft.NodejsTools.Project { + class NodejsProjectLauncher : IProjectLauncher { + private readonly NodejsProjectNode _project; + private int? _testServerPort; + + public NodejsProjectLauncher(NodejsProjectNode project) { + _project = project; + + var portNumber = _project.GetProjectProperty(NodejsConstants.NodejsPort); + int portNum; + if (Int32.TryParse(portNumber, out portNum)) { + _testServerPort = portNum; + } + } + + #region IProjectLauncher Members + + public int LaunchProject(bool debug) { + NodejsPackage.Instance.Logger.LogEvent(Logging.NodejsToolsLogEvent.Launch, debug ? 1 : 0); + return Start(ResolveStartupFile(), debug); + } + + public int LaunchFile(string file, bool debug) { + NodejsPackage.Instance.Logger.LogEvent(Logging.NodejsToolsLogEvent.Launch, debug ? 1 : 0); + return Start(file, debug); + } + + private int Start(string file, bool debug) { + string nodePath = GetNodePath(); + if (nodePath == null) { + Nodejs.ShowNodejsNotInstalled(); + return VSConstants.S_OK; + } + + bool startBrowser = ShouldStartBrowser(); + + if (debug) { + StartWithDebugger(file); + } else { + var psi = new ProcessStartInfo(); + psi.UseShellExecute = false; + + psi.FileName = nodePath; + psi.Arguments = GetFullArguments(file); + psi.WorkingDirectory = _project.GetWorkingDirectory(); + + string webBrowserUrl = GetFullUrl(); + Uri uri = null; + if (!String.IsNullOrWhiteSpace(webBrowserUrl)) { + uri = new Uri(webBrowserUrl); + + psi.EnvironmentVariables["PORT"] = uri.Port.ToString(); + } + + foreach (var nameValue in GetEnvironmentVariables()) { + psi.EnvironmentVariables[nameValue.Key] = nameValue.Value; + } + + var process = NodeProcess.Start( + psi, + NodejsPackage.Instance.GeneralOptionsPage.WaitOnAbnormalExit, + NodejsPackage.Instance.GeneralOptionsPage.WaitOnNormalExit + ); + + if (startBrowser && uri != null) { + OnPortOpenedHandler.CreateHandler( + uri.Port, + shortCircuitPredicate: () => process.HasExited, + action: () => { + VsShellUtilities.OpenBrowser(webBrowserUrl, (uint)__VSOSPFLAGS.OSP_LaunchNewBrowser); + } + ); + } + } + return VSConstants.S_OK; + } + + private string GetFullArguments(string file, bool includeNodeArgs = true) { + string res = String.Empty; + if (includeNodeArgs) { + var nodeArgs = _project.GetProjectProperty(NodejsConstants.NodeExeArguments); + if (!String.IsNullOrWhiteSpace(nodeArgs)) { + res = nodeArgs + " "; + } + } + res += "\"" + file + "\""; + var scriptArgs = _project.GetProjectProperty(NodejsConstants.ScriptArguments); + if (!String.IsNullOrWhiteSpace(scriptArgs)) { + res += " " + scriptArgs; + } + return res; + } + + private string GetNodePath() { + var overridePath = _project.GetProjectProperty(NodejsConstants.NodeExePath); + return Nodejs.GetAbsoluteNodeExePath(_project.ProjectHome, overridePath); + } + + #endregion + + private string GetFullUrl() { + var host = _project.GetProjectProperty(NodejsConstants.LaunchUrl); + + try { + return GetFullUrl(host, TestServerPort); + } catch (UriFormatException) { + var output = OutputWindowRedirector.GetGeneral(NodejsPackage.Instance); + output.WriteErrorLine(SR.GetString(SR.ErrorInvalidLaunchUrl, host)); + output.ShowAndActivate(); + return string.Empty; + } + } + + internal static string GetFullUrl(string host, int port) { + UriBuilder builder; + Uri uri; + if (Uri.TryCreate(host, UriKind.Absolute, out uri)) { + builder = new UriBuilder(uri); + } else { + builder = new UriBuilder(); + builder.Scheme = Uri.UriSchemeHttp; + builder.Host = "localhost"; + builder.Path = host; + } + + builder.Port = port; + + return builder.ToString(); + } + + private string TestServerPortString { + get { + if (!_testServerPort.HasValue) { + _testServerPort = GetFreePort(); + } + return _testServerPort.Value.ToString(CultureInfo.InvariantCulture); + } + } + + private int TestServerPort { + get { + if (!_testServerPort.HasValue) { + _testServerPort = GetFreePort(); + } + return _testServerPort.Value; + } + } + + /// + /// Default implementation of the "Start Debugging" command. + /// + private void StartWithDebugger(string startupFile) { + VsDebugTargetInfo dbgInfo = new VsDebugTargetInfo(); + dbgInfo.cbSize = (uint)Marshal.SizeOf(dbgInfo); + + if (SetupDebugInfo(ref dbgInfo, startupFile)) { + LaunchDebugger(_project.Site, dbgInfo); + } + } + + + private void LaunchDebugger(IServiceProvider provider, VsDebugTargetInfo dbgInfo) { + if (!Directory.Exists(dbgInfo.bstrCurDir)) { + MessageBox.Show(String.Format("Working directory \"{0}\" does not exist.", dbgInfo.bstrCurDir), "Node.js Tools for Visual Studio"); + } else if (!File.Exists(dbgInfo.bstrExe)) { + MessageBox.Show(String.Format("Interpreter \"{0}\" does not exist.", dbgInfo.bstrExe), "Node.js Tools for Visual Studio"); + } else if (DoesProjectSupportDebugging()) { + VsShellUtilities.LaunchDebugger(provider, dbgInfo); + } + } + + private bool DoesProjectSupportDebugging() { + var typeScriptOutFile = _project.GetProjectProperty("TypeScriptOutFile"); + if (!string.IsNullOrEmpty(typeScriptOutFile)) { + return MessageBox.Show( + "This TypeScript project has 'Combine Javascript output into file' option enabled. This option is not supported by NTVS debugger, " + + "and may result in erratic behavior of breakpoints, stepping, and debug tool windows. Are you sure you want to start debugging?", + SR.ProductName, + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning + ) == DialogResult.Yes; + } + + return true; + } + + private void AppendOption(ref VsDebugTargetInfo dbgInfo, string option, string value) { + if (!String.IsNullOrWhiteSpace(dbgInfo.bstrOptions)) { + dbgInfo.bstrOptions += ";"; + } + + dbgInfo.bstrOptions += option + "=" + HttpUtility.UrlEncode(value); + } + + /// + /// Sets up debugger information. + /// + private bool SetupDebugInfo(ref VsDebugTargetInfo dbgInfo, string startupFile) { + dbgInfo.dlo = DEBUG_LAUNCH_OPERATION.DLO_CreateProcess; + + dbgInfo.bstrExe = GetNodePath(); + dbgInfo.bstrCurDir = _project.GetWorkingDirectory(); + dbgInfo.bstrArg = GetFullArguments(startupFile, includeNodeArgs: false); // we need to supply node args via options + dbgInfo.bstrRemoteMachine = null; + var nodeArgs = _project.GetProjectProperty(NodejsConstants.NodeExeArguments); + if (!String.IsNullOrWhiteSpace(nodeArgs)) { + AppendOption(ref dbgInfo, AD7Engine.InterpreterOptions, nodeArgs); + } + + var url = GetFullUrl(); + if (ShouldStartBrowser() && !String.IsNullOrWhiteSpace(url)) { + AppendOption(ref dbgInfo, AD7Engine.WebBrowserUrl, url); + } + + var debuggerPort = _project.GetProjectProperty(NodejsConstants.DebuggerPort); + if (!String.IsNullOrWhiteSpace(debuggerPort)) { + AppendOption(ref dbgInfo, AD7Engine.DebuggerPort, debuggerPort); + } + + if (NodejsPackage.Instance.GeneralOptionsPage.WaitOnAbnormalExit) { + AppendOption(ref dbgInfo, AD7Engine.WaitOnAbnormalExitSetting, "true"); + } + + if (NodejsPackage.Instance.GeneralOptionsPage.WaitOnNormalExit) { + AppendOption(ref dbgInfo, AD7Engine.WaitOnNormalExitSetting, "true"); + } + + dbgInfo.fSendStdoutToOutputWindow = 0; + + StringDictionary env = new StringDictionary(); + if (!String.IsNullOrWhiteSpace(url)) { + Uri webUrl = new Uri(url); + env["PORT"] = webUrl.Port.ToString(); + } + + foreach (var nameValue in GetEnvironmentVariables()) { + env[nameValue.Key] = nameValue.Value; + } + + if (env.Count > 0) { + // add any inherited env vars + var variables = Environment.GetEnvironmentVariables(); + foreach (var key in variables.Keys) { + string strKey = (string)key; + if (!env.ContainsKey(strKey)) { + env.Add(strKey, (string)variables[key]); + } + } + + //Environemnt variables should be passed as a + //null-terminated block of null-terminated strings. + //Each string is in the following form:name=value\0 + StringBuilder buf = new StringBuilder(); + foreach (DictionaryEntry entry in env) { + buf.AppendFormat("{0}={1}\0", entry.Key, entry.Value); + } + buf.Append("\0"); + dbgInfo.bstrEnv = buf.ToString(); + } + + // Set the Node debugger + dbgInfo.clsidCustom = AD7Engine.DebugEngineGuid; + dbgInfo.grfLaunch = (uint)__VSDBGLAUNCHFLAGS.DBGLAUNCH_StopDebuggingOnEnd; + return true; + } + + private bool ShouldStartBrowser() { + var startBrowser = _project.GetProjectProperty(NodejsConstants.StartWebBrowser); + bool fStartBrowser; + if (!String.IsNullOrEmpty(startBrowser) && + Boolean.TryParse(startBrowser, out fStartBrowser)) { + return fStartBrowser; + } + + return true; + } + + private IEnumerable> GetEnvironmentVariables() { + var envVars = _project.GetProjectProperty(NodejsConstants.Environment); + if (envVars != null) { + foreach (var envVar in envVars.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries)) { + var nameValue = envVar.Split(new[] { '=' }, 2); + if (nameValue.Length == 2) { + yield return new KeyValuePair(nameValue[0], nameValue[1]); + } + } + } + } + + private static int GetFreePort() { + return Enumerable.Range(new Random().Next(1200, 2000), 60000).Except( + from connection in IPGlobalProperties.GetIPGlobalProperties().GetActiveTcpConnections() + select connection.LocalEndPoint.Port + ).First(); + } + + private string ResolveStartupFile() { + string startupFile = _project.GetStartupFile(); + if (string.IsNullOrEmpty(startupFile)) { + throw new ApplicationException("Please select a startup file to launch by right-clicking the file in Solution Explorer and selecting 'Set as Node.js Startup File' or by modifying your configuration in project properties."); + } + + if (TypeScriptHelpers.IsTypeScriptFile(startupFile)) { + startupFile = TypeScriptHelpers.GetTypeScriptBackedJavaScriptFile(_project, startupFile); + } + return startupFile; + } + } + + internal class OnPortOpenedHandler { + + class OnPortOpenedInfo { + public readonly int Port; + public readonly TimeSpan? Timeout; + public readonly int Sleep; + public readonly Func ShortCircuitPredicate; + public readonly Action Action; + public readonly DateTime StartTime; + + public OnPortOpenedInfo( + int port, + int? timeout = null, + int? sleep = null, + Func shortCircuitPredicate = null, + Action action = null + ) { + Port = port; + if (timeout.HasValue) { + Timeout = TimeSpan.FromMilliseconds(Convert.ToDouble(timeout)); + } + Sleep = sleep ?? 500; // 1/2 second sleep + ShortCircuitPredicate = shortCircuitPredicate ?? (() => false); + Action = action ?? (() => { }); + StartTime = System.DateTime.Now; + } + } + + internal static void CreateHandler( + int port, + int? timeout = null, + int? sleep = null, + Func shortCircuitPredicate = null, + Action action = null + ) { + ThreadPool.QueueUserWorkItem( + OnPortOpened, + new OnPortOpenedInfo( + port, + timeout, + sleep, + shortCircuitPredicate, + action + ) + ); + } + + private static void OnPortOpened(object infoObj) { + var info = (OnPortOpenedInfo)infoObj; + + using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) { + socket.Blocking = true; + try { + while (true) { + // Short circuit + if (info.ShortCircuitPredicate()) { + return; + } + + // Try connect + try { + socket.Connect(IPAddress.Loopback, info.Port); + break; + } catch { + // Connect failure + // Fall through + } + + // Timeout + if (info.Timeout.HasValue && (System.DateTime.Now - info.StartTime) >= info.Timeout) { + break; + } + + // Sleep + System.Threading.Thread.Sleep(info.Sleep); + } + } finally { + socket.Close(); + } + } + + // Launch browser (if not short-circuited) + if (!info.ShortCircuitPredicate()) { + info.Action(); + } + } + } +} diff --git a/Nodejs/Product/Nodejs/Project/NodejsProjectNode.cs b/Nodejs/Product/Nodejs/Project/NodejsProjectNode.cs index 2538209c5..7c1388592 100644 --- a/Nodejs/Product/Nodejs/Project/NodejsProjectNode.cs +++ b/Nodejs/Product/Nodejs/Project/NodejsProjectNode.cs @@ -1,1103 +1,1103 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Drawing; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.NodejsTools.Intellisense; -using Microsoft.NodejsTools.Npm; -using Microsoft.NodejsTools.ProjectWizard; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.Project; -using Microsoft.VisualStudioTools.Project.Automation; -using MSBuild = Microsoft.Build.Evaluation; -using VsCommands = Microsoft.VisualStudio.VSConstants.VSStd97CmdID; -#if DEV14_OR_LATER -using Microsoft.VisualStudio.Imaging.Interop; -using Microsoft.VisualStudio.Imaging; -#endif - -namespace Microsoft.NodejsTools.Project { - class NodejsProjectNode : CommonProjectNode, VsWebSite.VSWebSite, INodePackageModulesCommands, IVsBuildPropertyStorage { - private VsProjectAnalyzer _analyzer; - private readonly HashSet _warningFiles = new HashSet(); - private readonly HashSet _errorFiles = new HashSet(); - private string[] _analysisIgnoredDirs = new string[1] { NodejsConstants.NodeModulesStagingFolder }; - private int _maxFileSize = 1024 * 512; - internal readonly RequireCompletionCache _requireCompletionCache = new RequireCompletionCache(); - private string _intermediateOutputPath; - private readonly Dictionary _imageIndexFromNameDictionary = new Dictionary(); - - // We delay analysis until things calm down in the node_modules folder. - internal Queue DelayedAnalysisQueue = new Queue(); - private object _idleNodeModulesLock = new object(); - private volatile bool _isIdleNodeModules = false; - private Timer _idleNodeModulesTimer; - - public NodejsProjectNode(NodejsProjectPackage package) - : base( - package, -#if DEV14_OR_LATER - null -#else - Utilities.GetImageList(typeof(NodejsProjectNode).Assembly.GetManifestResourceStream("Microsoft.NodejsTools.Resources.Icons.NodejsImageList.bmp")) -#endif - ) { - Type projectNodePropsType = typeof(NodejsProjectNodeProperties); - AddCATIDMapping(projectNodePropsType, projectNodePropsType.GUID); -#pragma warning disable 0612 - InitNodejsProjectImages(); -#pragma warning restore 0612 - - } - - public VsProjectAnalyzer Analyzer { - get { - return _analyzer; - } - } - - private void OnIdleNodeModules(object state) { - lock (_idleNodeModulesLock) { - _isIdleNodeModules = true; - } - - while (DelayedAnalysisQueue.Count > 0) { - lock (_idleNodeModulesLock) { - if (!_isIdleNodeModules) { - return; - } - } - var fileNode = DelayedAnalysisQueue.Dequeue(); - if (fileNode != null) { - fileNode.Analyze(); - } - } - } - - internal void EnqueueForDelayedAnalysis(NodejsFileNode fileNode) { - DelayedAnalysisQueue.Enqueue(fileNode); - RestartIdleNodeModulesTimer(); - } - - private void RestartIdleNodeModulesTimer() { - lock (_idleNodeModulesLock) { - _isIdleNodeModules = false; - - // The cooldown time here is longer than the cooldown time we use in NpmController. - // This gives the Npm component ample time to build up the npm node tree, - // so that we can query it later for perf optimizations. - if (_idleNodeModulesTimer != null) { - _idleNodeModulesTimer.Change(3000, Timeout.Infinite); - } - } - } - - private static string[] _excludedAvailableItems = new[] { - "ApplicationDefinition", - "Page", - "Resource", - "SplashScreen", - "DesignData", - "DesignDataWithDesignTimeCreatableTypes", - "EntityDeploy", - "CodeAnalysisDictionary", - "XamlAppDef" - }; - - public override IEnumerable GetAvailableItemNames() { - // Remove a couple of available item names which show up from imports we - // can't control out of Microsoft.Common.targets. - return base.GetAvailableItemNames().Except(_excludedAvailableItems); - } - - public Dictionary ImageIndexFromNameDictionary { - get { return _imageIndexFromNameDictionary; } - } - -#if DEV14_OR_LATER - [Obsolete] -#endif - private void InitNodejsProjectImages() { - // HACK: https://nodejstools.codeplex.com/workitem/1268 - - // Project file images - AddProjectImage(NodejsProjectImageName.TypeScriptProjectFile, "Microsoft.VisualStudioTools.Resources.Icons.TSProject_SolutionExplorerNode.png"); - - // Dependency images - AddProjectImage(NodejsProjectImageName.Dependency, "Microsoft.VisualStudioTools.Resources.Icons.NodeJSPackage_16x.png"); - AddProjectImage(NodejsProjectImageName.DependencyNotListed, "Microsoft.VisualStudioTools.Resources.Icons.NodeJSPackageMissing_16x.png"); - AddProjectImage(NodejsProjectImageName.DependencyMissing, "Microsoft.VisualStudioTools.Resources.Icons.PackageWarning_16x.png"); - } - -#if DEV14_OR_LATER - protected override bool SupportsIconMonikers { - get { return true; } - } - - protected override ImageMoniker GetIconMoniker(bool open) { - if (string.Equals(GetProjectProperty(NodejsConstants.EnableTypeScript), "true", StringComparison.OrdinalIgnoreCase)) { - return KnownMonikers.TSProjectNode; - } - return KnownMonikers.JSProjectNode; - } - - [Obsolete] -#endif - private void AddProjectImage(NodejsProjectImageName name, string resourceId) { - var images = ImageHandler.ImageList.Images; - ImageIndexFromNameDictionary.Add(name, images.Count); - images.Add(Image.FromStream(typeof(NodejsProjectNode).Assembly.GetManifestResourceStream(resourceId))); - } - - public override Guid SharedCommandGuid { - get { - return Guids.NodejsCmdSet; - } - } - -#if !DEV14_OR_LATER - public override int ImageIndex { - get { - if (string.Equals(GetProjectProperty(NodejsConstants.EnableTypeScript), "true", StringComparison.OrdinalIgnoreCase)) { - return ImageIndexFromNameDictionary[NodejsProjectImageName.TypeScriptProjectFile]; - } - return base.ImageIndex; - } - } -#endif - internal override string IssueTrackerUrl { - get { return NodejsConstants.IssueTrackerUrl; } - } - - protected override void FinishProjectCreation(string sourceFolder, string destFolder) { - foreach (MSBuild.ProjectItem item in this.BuildProject.Items) { - if (String.Equals(Path.GetExtension(item.EvaluatedInclude), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { - - // Create the 'typings' folder - var typingsFolder = Path.Combine(ProjectHome, "Scripts", "typings"); - if (!Directory.Exists(typingsFolder)) { - Directory.CreateDirectory(typingsFolder); - } - - // Deploy node.d.ts - var nodeTypingsFolder = Path.Combine(typingsFolder, "node"); - if (!Directory.Exists(Path.Combine(nodeTypingsFolder))) { - Directory.CreateDirectory(nodeTypingsFolder); - } - - var nodeFolder = ((OAProject)this.GetAutomationObject()).ProjectItems - .AddFolder("Scripts").ProjectItems - .AddFolder("typings").ProjectItems - .AddFolder("node"); - - nodeFolder.ProjectItems.AddFromFileCopy( - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "Scripts", - "typings", - "node", - "node.d.ts" - ) - ); - break; - } - } - - base.FinishProjectCreation(sourceFolder, destFolder); - } - - protected override void AddNewFileNodeToHierarchy(HierarchyNode parentNode, string fileName) { - base.AddNewFileNodeToHierarchy(parentNode, fileName); - - if (String.Equals(Path.GetExtension(fileName), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase) && - !String.Equals(GetProjectProperty(NodejsConstants.EnableTypeScript), "true", StringComparison.OrdinalIgnoreCase)) { - // enable type script on the project automatically... - SetProjectProperty(NodejsConstants.EnableTypeScript, "true"); - SetProjectProperty(NodejsConstants.TypeScriptSourceMap, "true"); - if (String.IsNullOrWhiteSpace(GetProjectProperty(NodejsConstants.TypeScriptModuleKind))) { - SetProjectProperty(NodejsConstants.TypeScriptModuleKind, NodejsConstants.CommonJSModuleKind); - } - } - } - - internal static bool IsNodejsFile(string strFileName) { - var ext = Path.GetExtension(strFileName); - - return String.Equals(ext, NodejsConstants.JavaScriptExtension, StringComparison.OrdinalIgnoreCase); - } - - internal override string GetItemType(string filename) { - string absFileName = - Path.IsPathRooted(filename) ? - filename : - Path.Combine(this.ProjectHome, filename); - - var node = this.FindNodeByFullPath(absFileName) as NodejsFileNode; - if (node != null && node.ItemNode.ItemTypeName != null) { - return node.ItemNode.ItemTypeName; - } - - if (string.Equals(Path.GetExtension(filename), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { - return NodejsConstants.TypeScriptCompileItemType; - } - return base.GetItemType(filename); - } - - protected override bool DisableCmdInCurrentMode(Guid commandGroup, uint command) { - if (commandGroup == Guids.OfficeToolsBootstrapperCmdSet) { - // Convert to ... commands from Office Tools don't make sense and aren't supported - // on our project type - const int AddOfficeAppProject = 0x0001; - const int AddSharePointAppProject = 0x0002; - - if (command == AddOfficeAppProject || command == AddSharePointAppProject) { - return true; - } - } - - if (commandGroup == VSConstants.GUID_VSStandardCommandSet97) { - if (this.IsCurrentStateASuppressCommandsMode()) { - switch ((VsCommands)command) { - default: - break; - case VsCommands.UnloadProject: - case VsCommands.NewFolder: - case VsCommands.EditLabel: - case VsCommands.Rename: - return true; - } - } - } - - // don't defer to base class, Node allows edits while debugging (adding new files, etc...) - return false; - } - - public override string[] CodeFileExtensions { - get { - return new[] { NodejsConstants.JavaScriptExtension }; - } - } - - protected internal override FolderNode CreateFolderNode(ProjectElement element) { - return new NodejsFolderNode(this, element); - } - - public override CommonFileNode CreateCodeFileNode(ProjectElement item) { - string fileName = item.Url; - if (!String.IsNullOrWhiteSpace(fileName) - && Path.GetExtension(fileName).Equals(NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { - return new NodejsTypeScriptFileNode(this, item); - } - var res = new NodejsFileNode(this, item); - return res; - } - - public override CommonFileNode CreateNonCodeFileNode(ProjectElement item) { - string fileName = item.Url; - if (Path.GetFileName(fileName).Equals(NodejsConstants.PackageJsonFile, StringComparison.OrdinalIgnoreCase) && - !fileName.Contains(NodejsConstants.NodeModulesStagingFolder)) { - return new PackageJsonFileNode(this, item); - } - - return base.CreateNonCodeFileNode(item); - } - - public override string GetProjectName() { - return "NodeProject"; - } - - public override Type GetProjectFactoryType() { - return typeof(BaseNodeProjectFactory); - } - - public override Type GetEditorFactoryType() { - // Not presently used - throw new NotImplementedException(); - } - - public override string GetFormatList() { - return NodejsConstants.ProjectFileFilter; - } - - protected override Guid[] GetConfigurationDependentPropertyPages() { - var res = base.GetConfigurationDependentPropertyPages(); - - var enableTs = GetProjectProperty(NodejsConstants.EnableTypeScript, resetCache: false); - bool fEnableTs; - if (enableTs != null && Boolean.TryParse(enableTs, out fEnableTs) && fEnableTs) { - var typeScriptPages = GetProjectProperty(NodejsConstants.TypeScriptCfgProperty); - if (typeScriptPages != null) { - foreach (var strGuid in typeScriptPages.Split(';')) { - Guid guid; - if (Guid.TryParse(strGuid, out guid)) { - res = res.Append(guid); - } - } - } - } - - return res; - } - - public override Type GetGeneralPropertyPageType() { - return typeof(NodejsGeneralPropertyPage); - } - - public override Type GetLibraryManagerType() { - return typeof(NodejsLibraryManager); - } - - public override IProjectLauncher GetLauncher() { - return new NodejsProjectLauncher(this); - } - - protected override NodeProperties CreatePropertiesObject() { - return new NodejsProjectNodeProperties(this); - } - - protected override Stream ProjectIconsImageStripStream { - get { - return typeof(ProjectNode).Assembly.GetManifestResourceStream("Microsoft.VisualStudioTools.Resources.Icons.SharedProjectImageList.bmp"); - } - } - - public override bool IsCodeFile(string fileName) { - var ext = Path.GetExtension(fileName); - return ext.Equals(NodejsConstants.JavaScriptExtension, StringComparison.OrdinalIgnoreCase) || - ext.Equals(NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase); - } - - public override int InitializeForOuter(string filename, string location, string name, uint flags, ref Guid iid, out IntPtr projectPointer, out int canceled) { - NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevelChanged += IntellisenseOptionsPageAnalysisLevelChanged; - NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMaximumChanged += AnalysisLogMaximumChanged; - NodejsPackage.Instance.IntellisenseOptionsPage.SaveToDiskChanged += IntellisenseOptionsPageSaveToDiskChanged; - NodejsPackage.Instance.GeneralOptionsPage.ShowBrowserAndNodeLabelsChanged += ShowBrowserAndNodeLabelsChanged; - - return base.InitializeForOuter(filename, location, name, flags, ref iid, out projectPointer, out canceled); - } - - protected override void Reload() { - using (new DebugTimer("Project Load")) { - // Populate values from project properties before we do anything else. - // Otherwise we run into race conditions where, for instance, _analysisIgnoredDirectories - // is not properly set before the FileNodes get created in base.Reload() - UpdateProjectNodeFromProjectProperties(); - - if (_analyzer != null && _analyzer.RemoveUser()) { - _analyzer.Dispose(); - } - _analyzer = new VsProjectAnalyzer(ProjectFolder); - _analyzer.MaxLogLength = NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMax; - LogAnalysisLevel(); - - base.Reload(); - - SyncFileSystem(); - - NodejsPackage.Instance.CheckSurveyNews(false); - ModulesNode.ReloadHierarchySafe(); - - // scan for files which were loaded from cached analysis but no longer - // exist and remove them. - _analyzer.ReloadComplete(); - } - } - - private void UpdateProjectNodeFromProjectProperties() { - _intermediateOutputPath = Path.Combine(ProjectHome, GetProjectProperty("BaseIntermediateOutputPath")); - - var ignoredPaths = GetProjectProperty(NodejsConstants.AnalysisIgnoredDirectories); - - if (!string.IsNullOrWhiteSpace(ignoredPaths)) { - _analysisIgnoredDirs = _analysisIgnoredDirs.Append(ignoredPaths.Split(';').Select(x => '\\' + x + '\\').ToArray()); - } - - var maxFileSizeProp = GetProjectProperty(NodejsConstants.AnalysisMaxFileSize); - int maxFileSize; - if (maxFileSizeProp != null && Int32.TryParse(maxFileSizeProp, out maxFileSize)) { - _maxFileSize = maxFileSize; - } - } - - private void Reanalyze(HierarchyNode node, VsProjectAnalyzer newAnalyzer) { - if (node != null) { - for (var child = node.FirstChild; child != null; child = child.NextSibling) { - if (child is PackageJsonFileNode) { - ((PackageJsonFileNode)child).AnalyzePackageJson(newAnalyzer); - } else if (child is NodejsFileNode) { - if (((NodejsFileNode)child).ShouldAnalyze) { - newAnalyzer.AnalyzeFile(child.Url, !child.IsNonMemberItem); - } - } - - Reanalyze(child, newAnalyzer); - } - } - } - - private void LogAnalysisLevel() { - var analyzer = _analyzer; - if (analyzer != null) { - NodejsPackage.Instance.Logger.LogEvent(Logging.NodejsToolsLogEvent.AnalysisLevel, (int)analyzer.AnalysisLevel); - } - } - - /* - * Needed if we switch to per project Analysis levels - internal NodejsTools.Options.AnalysisLevel AnalysisLevel(){ - var analyzer = _analyzer; - if (_analyzer != null) { - return _analyzer.AnalysisLevel; - } - return NodejsTools.Options.AnalysisLevel.None; - } - */ - private void IntellisenseOptionsPageAnalysisLevelChanged(object sender, EventArgs e) { - - var oldAnalyzer = _analyzer; - _analyzer = null; - - var analyzer = new VsProjectAnalyzer(ProjectFolder); - Reanalyze(this, analyzer); - if (oldAnalyzer != null) { - analyzer.SwitchAnalyzers(oldAnalyzer); - if (oldAnalyzer.RemoveUser()) { - oldAnalyzer.Dispose(); - } - } - _analyzer = analyzer; - _analyzer.MaxLogLength = NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMax; - LogAnalysisLevel(); - } - - private void AnalysisLogMaximumChanged(object sender, EventArgs e) { - if (_analyzer != null) { - _analyzer.MaxLogLength = NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMax; - } - } - - private void IntellisenseOptionsPageSaveToDiskChanged(object sender, EventArgs e) { - if (_analyzer != null) { - _analyzer.SaveToDisk = NodejsPackage.Instance.IntellisenseOptionsPage.SaveToDisk; - } - } - - private void ShowBrowserAndNodeLabelsChanged(object sender, EventArgs e) { - var nodejsFolderNodes = this.AllDescendants.Where(item => (item as NodejsFolderNode) != null).Select(item => (NodejsFolderNode)item); - foreach (var node in nodejsFolderNodes) { - ProjectMgr.ReDrawNode(node, UIHierarchyElement.Caption); - } - } - - protected override void RaiseProjectPropertyChanged(string propertyName, string oldValue, string newValue) { - base.RaiseProjectPropertyChanged(propertyName, oldValue, newValue); - - var propPage = GeneralPropertyPageControl; - if (propPage != null) { - switch (propertyName) { - case NodejsConstants.Environment: - propPage.Environment = newValue; - break; - case NodejsConstants.DebuggerPort: - propPage.DebuggerPort = newValue; - break; - case NodejsConstants.NodejsPort: - propPage.NodejsPort = newValue; - break; - case NodejsConstants.NodeExePath: - propPage.NodeExePath = newValue; - break; - case NodejsConstants.NodeExeArguments: - propPage.NodeExeArguments = newValue; - break; - case CommonConstants.StartupFile: - propPage.ScriptFile = newValue; - break; - case NodejsConstants.ScriptArguments: - propPage.ScriptArguments = newValue; - break; - case NodejsConstants.LaunchUrl: - propPage.LaunchUrl = newValue; - break; - case NodejsConstants.StartWebBrowser: - bool value; - if (Boolean.TryParse(newValue, out value)) { - propPage.StartWebBrowser = value; - } - break; - case CommonConstants.WorkingDirectory: - propPage.WorkingDirectory = newValue; - break; - default: - if (propPage != null) { - PropertyPage.IsDirty = true; - } - break; - } - } - } - - private NodejsGeneralPropertyPageControl GeneralPropertyPageControl { - get { - if (PropertyPage != null && PropertyPage.Control != null) { - return (NodejsGeneralPropertyPageControl)PropertyPage.Control; - } - - return null; - } - } - - private static void AddFolderForFile(Dictionary> directoryPackages, FileNode rootFile, CommonFolderNode folderChild) { - List folders; - if (!directoryPackages.TryGetValue(rootFile, out folders)) { - directoryPackages[rootFile] = folders = new List(); - } - folders.Add(folderChild); - } - - protected override bool IncludeNonMemberItemInProject(HierarchyNode node) { - var fileNode = node as NodejsFileNode; - if (fileNode != null) { - return IncludeNodejsFile(fileNode); - } - return false; - } - - internal bool IncludeNodejsFile(NodejsFileNode fileNode) { - var url = fileNode.Url; - if (CommonUtils.IsSubpathOf(_intermediateOutputPath, fileNode.Url)) { - return false; - } - - foreach (var path in _analysisIgnoredDirs) { - if (url.IndexOf(path, 0, StringComparison.OrdinalIgnoreCase) != -1) { - return false; - } - } - - var fileInfo = new FileInfo(fileNode.Url); - if (!fileInfo.Exists || fileInfo.Length > _maxFileSize) { - // skip obviously generated and missing files... - return false; - } - - int nestedModulesDepth = 0; - if (ModulesNode.NpmController.RootPackage != null && ModulesNode.NpmController.RootPackage.Modules != null) { - nestedModulesDepth = ModulesNode.NpmController.RootPackage.Modules.GetDepth(fileNode.Url); - } - - if (_analyzer != null && _analyzer.Project != null && - _analyzer.Project.Limits.IsPathExceedNestingLimit(nestedModulesDepth)) { - return false; - } - - return true; - } - - internal override object Object { - get { - return this; - } - } - - protected override ReferenceContainerNode CreateReferenceContainerNode() { - return null; - } - - public NodeModulesNode ModulesNode { get; private set; } - - - - protected internal override void ProcessReferences() { - base.ProcessReferences(); - - if (null == ModulesNode) { - ModulesNode = new NodeModulesNode(this); - AddChild(ModulesNode); - _idleNodeModulesTimer = new Timer(OnIdleNodeModules); - } - } - - #region VSWebSite Members - - // This interface is just implemented so we don't get normal profiling which - // doesn't work with our projects anyway. - - public EnvDTE.ProjectItem AddFromTemplate(string bstrRelFolderUrl, string bstrWizardName, string bstrLanguage, string bstrItemName, bool bUseCodeSeparation, string bstrMasterPage, string bstrDocType) { - throw new NotImplementedException(); - } - - public VsWebSite.CodeFolders CodeFolders { - get { throw new NotImplementedException(); } - } - - public EnvDTE.DTE DTE { - get { return Project.DTE; } - } - - public string EnsureServerRunning() { - throw new NotImplementedException(); - } - - public string GetUniqueFilename(string bstrFolder, string bstrRoot, string bstrDesiredExt) { - throw new NotImplementedException(); - } - - public bool PreCompileWeb(string bstrCompilePath, bool bUpdateable) { - throw new NotImplementedException(); - } - - public EnvDTE.Project Project { - get { return (OAProject)GetAutomationObject(); } - } - - public VsWebSite.AssemblyReferences References { - get { throw new NotImplementedException(); } - } - - public void Refresh() { - } - - public string TemplatePath { - get { throw new NotImplementedException(); } - } - - public string URL { - get { throw new NotImplementedException(); } - } - - public string UserTemplatePath { - get { throw new NotImplementedException(); } - } - - public VsWebSite.VSWebSiteEvents VSWebSiteEvents { - get { throw new NotImplementedException(); } - } - - public void WaitUntilReady() { - } - - public VsWebSite.WebReferences WebReferences { - get { throw new NotImplementedException(); } - } - - public VsWebSite.WebServices WebServices { - get { throw new NotImplementedException(); } - } - - #endregion - - Task INodePackageModulesCommands.InstallMissingModulesAsync() { - //Fire off the command to update the missing modules - // through NPM - return ModulesNode.InstallMissingModules(); - } - - private void HookErrorsAndWarnings(VsProjectAnalyzer res) { - res.ErrorAdded += OnErrorAdded; - res.ErrorRemoved += OnErrorRemoved; - res.WarningAdded += OnWarningAdded; - res.WarningRemoved += OnWarningRemoved; - } - - private void UnHookErrorsAndWarnings(VsProjectAnalyzer res) { - res.ErrorAdded -= OnErrorAdded; - res.ErrorRemoved -= OnErrorRemoved; - res.WarningAdded -= OnWarningAdded; - res.WarningRemoved -= OnWarningRemoved; - } - - private void OnErrorAdded(object sender, FileEventArgs args) { - if (_diskNodes.ContainsKey(args.Filename)) { - _errorFiles.Add(args.Filename); - } - } - - private void OnErrorRemoved(object sender, FileEventArgs args) { - _errorFiles.Remove(args.Filename); - } - - private void OnWarningAdded(object sender, FileEventArgs args) { - if (_diskNodes.ContainsKey(args.Filename)) { - _warningFiles.Add(args.Filename); - } - } - - private void OnWarningRemoved(object sender, FileEventArgs args) { - _warningFiles.Remove(args.Filename); - } - - /// - /// File names within the project which contain errors. - /// - public HashSet ErrorFiles { - get { - return _errorFiles; - } - } - - /// - /// File names within the project which contain warnings. - /// - public HashSet WarningFiles { - get { - return _warningFiles; - } - } - - internal struct LongPathInfo { - public string FullPath; - public string RelativePath; - public bool IsDirectory; - } - - private static readonly Regex _uninstallRegex = new Regex(@"\b(uninstall|rm)\b"); - private static readonly char[] _pathSeparators = { '\\', '/' }; - private bool _isCheckingForLongPaths; - - public async Task CheckForLongPaths(string npmArguments = null) { - if (_isCheckingForLongPaths || !NodejsPackage.Instance.GeneralOptionsPage.CheckForLongPaths) { - return; - } - - if (npmArguments != null && _uninstallRegex.IsMatch(npmArguments)) { - return; - } - - try { - _isCheckingForLongPaths = true; - TaskDialogButton dedupeButton, ignoreButton, disableButton; - var taskDialog = new TaskDialog(NodejsPackage.Instance) { - AllowCancellation = true, - EnableHyperlinks = true, - Title = SR.GetString(SR.LongPathWarningTitle), - MainIcon = TaskDialogIcon.Warning, - Content = SR.GetString(SR.LongPathWarningText), - CollapsedControlText = SR.GetString(SR.LongPathShowPathsExceedingTheLimit), - ExpandedControlText = SR.GetString(SR.LongPathHidePathsExceedingTheLimit), - Buttons = { - (dedupeButton = new TaskDialogButton(SR.GetString(SR.LongPathNpmDedupe), SR.GetString(SR.LongPathNpmDedupeDetail))), - (ignoreButton = new TaskDialogButton(SR.GetString(SR.LongPathDoNothingButWarnNextTime))), - (disableButton = new TaskDialogButton(SR.GetString(SR.LongPathDoNothingAndDoNotWarnAgain), SR.GetString(SR.LongPathDoNothingAndDoNotWarnAgainDetail))) - }, - FooterIcon = TaskDialogIcon.Information, - Footer = SR.GetString(SR.LongPathFooter) - }; - - taskDialog.HyperlinkClicked += (sender, e) => { - switch (e.Url) { - case "#msdn": - Process.Start("http://go.microsoft.com/fwlink/?LinkId=454508"); - break; - case "#uservoice": - Process.Start("http://go.microsoft.com/fwlink/?LinkID=456509"); - break; - case "#help": - Process.Start("http://go.microsoft.com/fwlink/?LinkId=456511"); - break; - default: - System.Windows.Clipboard.SetText(e.Url); - break; - } - }; - - recheck: - - var longPaths = await Task.Factory.StartNew(() => - GetLongSubPaths(ProjectHome) - .Concat(GetLongSubPaths(_intermediateOutputPath)) - .Select(lpi => string.Format("• {1}\u00A0{2}", lpi.FullPath, lpi.RelativePath, SR.GetString(SR.LongPathClickToCopy))) - .ToArray()); - if (longPaths.Length == 0) { - return; - } - taskDialog.ExpandedInformation = string.Join("\r\n", longPaths); - - var button = taskDialog.ShowModal(); - if (button == dedupeButton) { - var repl = NodejsPackage.Instance.OpenReplWindow(focus: false); - await repl.ExecuteCommand(".npm dedupe").HandleAllExceptions(SR.ProductName); - - taskDialog.Content += "\r\n\r\n" + SR.GetString(SR.LongPathNpmDedupeDidNotHelp); - taskDialog.Buttons.Remove(dedupeButton); - goto recheck; - } else if (button == disableButton) { - var page = NodejsPackage.Instance.GeneralOptionsPage; - page.CheckForLongPaths = false; - page.SaveSettingsToStorage(); - } - } finally { - _isCheckingForLongPaths = false; - } - } - - internal static IEnumerable GetLongSubPaths(string basePath, string path = "") { - const int MaxFilePathLength = 260 - 1; // account for terminating NULL - const int MaxDirectoryPathLength = 248 - 1; - - basePath = CommonUtils.EnsureEndSeparator(basePath); - - WIN32_FIND_DATA wfd; - IntPtr hFind = NativeMethods.FindFirstFile(basePath + path + "\\*", out wfd); - if (hFind == NativeMethods.INVALID_HANDLE_VALUE) { - yield break; - } - - try { - do { - if (wfd.cFileName == "." || wfd.cFileName == "..") { - continue; - } - - bool isDirectory = (wfd.dwFileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) != 0; - - string childPath = path; - if (childPath != String.Empty) { - childPath += "\\"; - } - childPath += wfd.cFileName; - - string fullChildPath = basePath + childPath; - bool isTooLong; - try { - isTooLong = Path.GetFullPath(fullChildPath).Length > (isDirectory ? MaxDirectoryPathLength : MaxFilePathLength); - } catch (PathTooLongException) { - isTooLong = true; - } catch (Exception) { - continue; - } - - if (isTooLong) { - yield return new LongPathInfo { FullPath = fullChildPath, RelativePath = childPath, IsDirectory = isDirectory }; - } else if (isDirectory) { - foreach (var item in GetLongSubPaths(basePath, childPath)) { - yield return item; - } - } - } while (NativeMethods.FindNextFile(hFind, out wfd)); - } finally { - NativeMethods.FindClose(hFind); - } - } - - protected override void Dispose(bool disposing) { - if (disposing) { - if (_analyzer != null) { - UnHookErrorsAndWarnings(_analyzer); - if (WarningFiles.Count > 0 || ErrorFiles.Count > 0) { - foreach (var file in WarningFiles.Concat(ErrorFiles)) { - var node = FindNodeByFullPath(file) as NodejsFileNode; - if (node != null) { - //_analyzer.RemoveErrors(node.GetAnalysis(), suppressUpdate: false); - } - } - } - - if (_analyzer.RemoveUser()) { - _analyzer.Dispose(); - } - _analyzer = null; - } - - lock (_idleNodeModulesLock) { - if (_idleNodeModulesTimer != null) { - _idleNodeModulesTimer.Dispose(); - } - _idleNodeModulesTimer = null; - } - - NodejsPackage.Instance.IntellisenseOptionsPage.SaveToDiskChanged -= IntellisenseOptionsPageSaveToDiskChanged; - NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevelChanged -= IntellisenseOptionsPageAnalysisLevelChanged; - NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMaximumChanged -= AnalysisLogMaximumChanged; - } - base.Dispose(disposing); - } - - internal override async void BuildAsync(uint vsopts, string config, VisualStudio.Shell.Interop.IVsOutputWindowPane output, string target, Action uiThreadCallback) { - try { - await CheckForLongPaths(); - } catch (Exception) { - uiThreadCallback(MSBuildResult.Failed, target); - return; - } - - // BuildAsync can throw on the sync path before invoking the callback. If it does, we must still invoke the callback here, - // because by this time there's no other way to propagate the error to the caller. - try { - base.BuildAsync(vsopts, config, output, target, uiThreadCallback); - } catch (Exception) { - uiThreadCallback(MSBuildResult.Failed, target); - } - } - - internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { - if (cmdGroup == Guids.NodejsCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidOpenReplWindow: - result = QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; - return VSConstants.S_OK; - } - } - return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); - } - - protected override QueryStatusResult QueryStatusSelectionOnNodes(IList selectedNodes, Guid cmdGroup, uint cmd, IntPtr pCmdText) { - if (cmdGroup == Guids.NodejsNpmCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidNpmManageModules: - if (IsCurrentStateASuppressCommandsMode()) { - return QueryStatusResult.SUPPORTED; - } else if (!ShowManageModulesCommandOnNode(selectedNodes)) { - return QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED | QueryStatusResult.INVISIBLE; - } - return QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; - } - } else if (cmdGroup == Guids.NodejsCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidSetAsNodejsStartupFile: - if (ShowSetAsStartupFileCommandOnNode(selectedNodes)) { - // We enable "Set as StartUp File" command only on current language code files, - // the file is in project home dir and if the file is not the startup file already. - string startupFile = ((CommonProjectNode)ProjectMgr).GetStartupFile(); - if (!CommonUtils.IsSamePath(startupFile, selectedNodes[0].Url)) { - return QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; - } - } - break; - } - } - - return base.QueryStatusSelectionOnNodes(selectedNodes, cmdGroup, cmd, pCmdText); - } - - internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { - if (cmdGroup == Guids.NodejsCmdSet) { - switch (cmd) { - case PkgCmdId.cmdidOpenReplWindow: - NodejsPackage.Instance.OpenReplWindow(); - return VSConstants.S_OK; - } - } else if (cmdGroup == Guids.NodejsNpmCmdSet) { - try { - NpmHelpers.GetPathToNpm( - Nodejs.GetAbsoluteNodeExePath( - ProjectHome, - Project.GetNodejsProject().GetProjectProperty(NodejsConstants.NodeExePath) - )); - } catch (NpmNotFoundException) { - Nodejs.ShowNodejsNotInstalled(); - return VSConstants.S_OK; - } - } - return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); - } - - protected override int ExecCommandThatDependsOnSelectedNodes(Guid cmdGroup, uint cmdId, uint cmdExecOpt, IntPtr vaIn, IntPtr vaOut, CommandOrigin commandOrigin, IList selectedNodes, out bool handled) { - if (cmdGroup == Guids.NodejsNpmCmdSet) { - try { - NpmHelpers.GetPathToNpm( - Nodejs.GetAbsoluteNodeExePath( - ProjectHome, - Project.GetNodejsProject().GetProjectProperty(NodejsConstants.NodeExePath) - )); - } catch (NpmNotFoundException) { - Nodejs.ShowNodejsNotInstalled(); - handled = true; - return VSConstants.S_OK; - } - - switch (cmdId) { - case PkgCmdId.cmdidNpmManageModules: - if (!ShowManageModulesCommandOnNode(selectedNodes)) { - ModulesNode.ManageModules(); - handled = true; - return VSConstants.S_OK; - } - - var node = selectedNodes[0] as AbstractNpmNode; - if (node != null) { - var abstractNpmNode = node; - abstractNpmNode.ManageNpmModules(); - handled = true; - return VSConstants.S_OK; - } - break; - } - } else if (cmdGroup == Guids.NodejsCmdSet) { - switch (cmdId) { - case PkgCmdId.cmdidSetAsNodejsStartupFile: - // Set the StartupFile project property to the Url of this node - SetProjectProperty( - CommonConstants.StartupFile, - CommonUtils.GetRelativeFilePath(ProjectHome, selectedNodes[0].Url) - ); - handled = true; - return VSConstants.S_OK; - } - } - - return base.ExecCommandThatDependsOnSelectedNodes(cmdGroup, cmdId, cmdExecOpt, vaIn, vaOut, commandOrigin, selectedNodes, out handled); - } - - private bool ShowSetAsStartupFileCommandOnNode(IList selectedNodes) { - var selectedNodeUrl = selectedNodes[0].Url; - return selectedNodes.Count == 1 && - (IsCodeFile(selectedNodeUrl) || - // for some reason, the default express 4 template's startup file lacks an extension. - string.IsNullOrEmpty(Path.GetExtension(selectedNodeUrl))); - } - - private static bool ShowManageModulesCommandOnNode(IList selectedNodes) { - return selectedNodes.Count == 1 && selectedNodes[0] is AbstractNpmNode; - } - - protected internal override void SetCurrentConfiguration() { - if (!IsProjectOpened) { - return; - } - - if (this.IsPlatformAware()) { - EnvDTE.Project automationObject = GetAutomationObject() as EnvDTE.Project; - - this.BuildProject.SetGlobalProperty(ProjectFileConstants.Platform, automationObject.ConfigurationManager.ActiveConfiguration.PlatformName); - } - base.SetCurrentConfiguration(); - } - - public override MSBuildResult Build(string config, string target) { - if (this.IsPlatformAware()) { - var platform = this.BuildProject.GetPropertyValue(GlobalProperty.Platform.ToString()); - - if (platform == ProjectConfig.AnyCPU) { - this.BuildProject.SetGlobalProperty(GlobalProperty.Platform.ToString(), ConfigProvider.x86Platform); - } - } - return base.Build(config, target); - } - - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.NodejsTools.Intellisense; +using Microsoft.NodejsTools.Npm; +using Microsoft.NodejsTools.ProjectWizard; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.Project; +using Microsoft.VisualStudioTools.Project.Automation; +using MSBuild = Microsoft.Build.Evaluation; +using VsCommands = Microsoft.VisualStudio.VSConstants.VSStd97CmdID; +#if DEV14_OR_LATER +using Microsoft.VisualStudio.Imaging.Interop; +using Microsoft.VisualStudio.Imaging; +#endif + +namespace Microsoft.NodejsTools.Project { + class NodejsProjectNode : CommonProjectNode, VsWebSite.VSWebSite, INodePackageModulesCommands, IVsBuildPropertyStorage { + private VsProjectAnalyzer _analyzer; + private readonly HashSet _warningFiles = new HashSet(); + private readonly HashSet _errorFiles = new HashSet(); + private string[] _analysisIgnoredDirs = new string[1] { NodejsConstants.NodeModulesStagingFolder }; + private int _maxFileSize = 1024 * 512; + internal readonly RequireCompletionCache _requireCompletionCache = new RequireCompletionCache(); + private string _intermediateOutputPath; + private readonly Dictionary _imageIndexFromNameDictionary = new Dictionary(); + + // We delay analysis until things calm down in the node_modules folder. + internal Queue DelayedAnalysisQueue = new Queue(); + private object _idleNodeModulesLock = new object(); + private volatile bool _isIdleNodeModules = false; + private Timer _idleNodeModulesTimer; + + public NodejsProjectNode(NodejsProjectPackage package) + : base( + package, +#if DEV14_OR_LATER + null +#else + Utilities.GetImageList(typeof(NodejsProjectNode).Assembly.GetManifestResourceStream("Microsoft.NodejsTools.Resources.Icons.NodejsImageList.bmp")) +#endif + ) { + Type projectNodePropsType = typeof(NodejsProjectNodeProperties); + AddCATIDMapping(projectNodePropsType, projectNodePropsType.GUID); +#pragma warning disable 0612 + InitNodejsProjectImages(); +#pragma warning restore 0612 + + } + + public VsProjectAnalyzer Analyzer { + get { + return _analyzer; + } + } + + private void OnIdleNodeModules(object state) { + lock (_idleNodeModulesLock) { + _isIdleNodeModules = true; + } + + while (DelayedAnalysisQueue.Count > 0) { + lock (_idleNodeModulesLock) { + if (!_isIdleNodeModules) { + return; + } + } + var fileNode = DelayedAnalysisQueue.Dequeue(); + if (fileNode != null) { + fileNode.Analyze(); + } + } + } + + internal void EnqueueForDelayedAnalysis(NodejsFileNode fileNode) { + DelayedAnalysisQueue.Enqueue(fileNode); + RestartIdleNodeModulesTimer(); + } + + private void RestartIdleNodeModulesTimer() { + lock (_idleNodeModulesLock) { + _isIdleNodeModules = false; + + // The cooldown time here is longer than the cooldown time we use in NpmController. + // This gives the Npm component ample time to build up the npm node tree, + // so that we can query it later for perf optimizations. + if (_idleNodeModulesTimer != null) { + _idleNodeModulesTimer.Change(3000, Timeout.Infinite); + } + } + } + + private static string[] _excludedAvailableItems = new[] { + "ApplicationDefinition", + "Page", + "Resource", + "SplashScreen", + "DesignData", + "DesignDataWithDesignTimeCreatableTypes", + "EntityDeploy", + "CodeAnalysisDictionary", + "XamlAppDef" + }; + + public override IEnumerable GetAvailableItemNames() { + // Remove a couple of available item names which show up from imports we + // can't control out of Microsoft.Common.targets. + return base.GetAvailableItemNames().Except(_excludedAvailableItems); + } + + public Dictionary ImageIndexFromNameDictionary { + get { return _imageIndexFromNameDictionary; } + } + +#if DEV14_OR_LATER + [Obsolete] +#endif + private void InitNodejsProjectImages() { + // HACK: https://nodejstools.codeplex.com/workitem/1268 + + // Project file images + AddProjectImage(NodejsProjectImageName.TypeScriptProjectFile, "Microsoft.VisualStudioTools.Resources.Icons.TSProject_SolutionExplorerNode.png"); + + // Dependency images + AddProjectImage(NodejsProjectImageName.Dependency, "Microsoft.VisualStudioTools.Resources.Icons.NodeJSPackage_16x.png"); + AddProjectImage(NodejsProjectImageName.DependencyNotListed, "Microsoft.VisualStudioTools.Resources.Icons.NodeJSPackageMissing_16x.png"); + AddProjectImage(NodejsProjectImageName.DependencyMissing, "Microsoft.VisualStudioTools.Resources.Icons.PackageWarning_16x.png"); + } + +#if DEV14_OR_LATER + protected override bool SupportsIconMonikers { + get { return true; } + } + + protected override ImageMoniker GetIconMoniker(bool open) { + if (string.Equals(GetProjectProperty(NodejsConstants.EnableTypeScript), "true", StringComparison.OrdinalIgnoreCase)) { + return KnownMonikers.TSProjectNode; + } + return KnownMonikers.JSProjectNode; + } + + [Obsolete] +#endif + private void AddProjectImage(NodejsProjectImageName name, string resourceId) { + var images = ImageHandler.ImageList.Images; + ImageIndexFromNameDictionary.Add(name, images.Count); + images.Add(Image.FromStream(typeof(NodejsProjectNode).Assembly.GetManifestResourceStream(resourceId))); + } + + public override Guid SharedCommandGuid { + get { + return Guids.NodejsCmdSet; + } + } + +#if !DEV14_OR_LATER + public override int ImageIndex { + get { + if (string.Equals(GetProjectProperty(NodejsConstants.EnableTypeScript), "true", StringComparison.OrdinalIgnoreCase)) { + return ImageIndexFromNameDictionary[NodejsProjectImageName.TypeScriptProjectFile]; + } + return base.ImageIndex; + } + } +#endif + internal override string IssueTrackerUrl { + get { return NodejsConstants.IssueTrackerUrl; } + } + + protected override void FinishProjectCreation(string sourceFolder, string destFolder) { + foreach (MSBuild.ProjectItem item in this.BuildProject.Items) { + if (String.Equals(Path.GetExtension(item.EvaluatedInclude), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { + + // Create the 'typings' folder + var typingsFolder = Path.Combine(ProjectHome, "Scripts", "typings"); + if (!Directory.Exists(typingsFolder)) { + Directory.CreateDirectory(typingsFolder); + } + + // Deploy node.d.ts + var nodeTypingsFolder = Path.Combine(typingsFolder, "node"); + if (!Directory.Exists(Path.Combine(nodeTypingsFolder))) { + Directory.CreateDirectory(nodeTypingsFolder); + } + + var nodeFolder = ((OAProject)this.GetAutomationObject()).ProjectItems + .AddFolder("Scripts").ProjectItems + .AddFolder("typings").ProjectItems + .AddFolder("node"); + + nodeFolder.ProjectItems.AddFromFileCopy( + Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "Scripts", + "typings", + "node", + "node.d.ts" + ) + ); + break; + } + } + + base.FinishProjectCreation(sourceFolder, destFolder); + } + + protected override void AddNewFileNodeToHierarchy(HierarchyNode parentNode, string fileName) { + base.AddNewFileNodeToHierarchy(parentNode, fileName); + + if (String.Equals(Path.GetExtension(fileName), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase) && + !String.Equals(GetProjectProperty(NodejsConstants.EnableTypeScript), "true", StringComparison.OrdinalIgnoreCase)) { + // enable type script on the project automatically... + SetProjectProperty(NodejsConstants.EnableTypeScript, "true"); + SetProjectProperty(NodejsConstants.TypeScriptSourceMap, "true"); + if (String.IsNullOrWhiteSpace(GetProjectProperty(NodejsConstants.TypeScriptModuleKind))) { + SetProjectProperty(NodejsConstants.TypeScriptModuleKind, NodejsConstants.CommonJSModuleKind); + } + } + } + + internal static bool IsNodejsFile(string strFileName) { + var ext = Path.GetExtension(strFileName); + + return String.Equals(ext, NodejsConstants.JavaScriptExtension, StringComparison.OrdinalIgnoreCase); + } + + internal override string GetItemType(string filename) { + string absFileName = + Path.IsPathRooted(filename) ? + filename : + Path.Combine(this.ProjectHome, filename); + + var node = this.FindNodeByFullPath(absFileName) as NodejsFileNode; + if (node != null && node.ItemNode.ItemTypeName != null) { + return node.ItemNode.ItemTypeName; + } + + if (string.Equals(Path.GetExtension(filename), NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { + return NodejsConstants.TypeScriptCompileItemType; + } + return base.GetItemType(filename); + } + + protected override bool DisableCmdInCurrentMode(Guid commandGroup, uint command) { + if (commandGroup == Guids.OfficeToolsBootstrapperCmdSet) { + // Convert to ... commands from Office Tools don't make sense and aren't supported + // on our project type + const int AddOfficeAppProject = 0x0001; + const int AddSharePointAppProject = 0x0002; + + if (command == AddOfficeAppProject || command == AddSharePointAppProject) { + return true; + } + } + + if (commandGroup == VSConstants.GUID_VSStandardCommandSet97) { + if (this.IsCurrentStateASuppressCommandsMode()) { + switch ((VsCommands)command) { + default: + break; + case VsCommands.UnloadProject: + case VsCommands.NewFolder: + case VsCommands.EditLabel: + case VsCommands.Rename: + return true; + } + } + } + + // don't defer to base class, Node allows edits while debugging (adding new files, etc...) + return false; + } + + public override string[] CodeFileExtensions { + get { + return new[] { NodejsConstants.JavaScriptExtension }; + } + } + + protected internal override FolderNode CreateFolderNode(ProjectElement element) { + return new NodejsFolderNode(this, element); + } + + public override CommonFileNode CreateCodeFileNode(ProjectElement item) { + string fileName = item.Url; + if (!String.IsNullOrWhiteSpace(fileName) + && Path.GetExtension(fileName).Equals(NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase)) { + return new NodejsTypeScriptFileNode(this, item); + } + var res = new NodejsFileNode(this, item); + return res; + } + + public override CommonFileNode CreateNonCodeFileNode(ProjectElement item) { + string fileName = item.Url; + if (Path.GetFileName(fileName).Equals(NodejsConstants.PackageJsonFile, StringComparison.OrdinalIgnoreCase) && + !fileName.Contains(NodejsConstants.NodeModulesStagingFolder)) { + return new PackageJsonFileNode(this, item); + } + + return base.CreateNonCodeFileNode(item); + } + + public override string GetProjectName() { + return "NodeProject"; + } + + public override Type GetProjectFactoryType() { + return typeof(BaseNodeProjectFactory); + } + + public override Type GetEditorFactoryType() { + // Not presently used + throw new NotImplementedException(); + } + + public override string GetFormatList() { + return NodejsConstants.ProjectFileFilter; + } + + protected override Guid[] GetConfigurationDependentPropertyPages() { + var res = base.GetConfigurationDependentPropertyPages(); + + var enableTs = GetProjectProperty(NodejsConstants.EnableTypeScript, resetCache: false); + bool fEnableTs; + if (enableTs != null && Boolean.TryParse(enableTs, out fEnableTs) && fEnableTs) { + var typeScriptPages = GetProjectProperty(NodejsConstants.TypeScriptCfgProperty); + if (typeScriptPages != null) { + foreach (var strGuid in typeScriptPages.Split(';')) { + Guid guid; + if (Guid.TryParse(strGuid, out guid)) { + res = res.Append(guid); + } + } + } + } + + return res; + } + + public override Type GetGeneralPropertyPageType() { + return typeof(NodejsGeneralPropertyPage); + } + + public override Type GetLibraryManagerType() { + return typeof(NodejsLibraryManager); + } + + public override IProjectLauncher GetLauncher() { + return new NodejsProjectLauncher(this); + } + + protected override NodeProperties CreatePropertiesObject() { + return new NodejsProjectNodeProperties(this); + } + + protected override Stream ProjectIconsImageStripStream { + get { + return typeof(ProjectNode).Assembly.GetManifestResourceStream("Microsoft.VisualStudioTools.Resources.Icons.SharedProjectImageList.bmp"); + } + } + + public override bool IsCodeFile(string fileName) { + var ext = Path.GetExtension(fileName); + return ext.Equals(NodejsConstants.JavaScriptExtension, StringComparison.OrdinalIgnoreCase) || + ext.Equals(NodejsConstants.TypeScriptExtension, StringComparison.OrdinalIgnoreCase); + } + + public override int InitializeForOuter(string filename, string location, string name, uint flags, ref Guid iid, out IntPtr projectPointer, out int canceled) { + NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevelChanged += IntellisenseOptionsPageAnalysisLevelChanged; + NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMaximumChanged += AnalysisLogMaximumChanged; + NodejsPackage.Instance.IntellisenseOptionsPage.SaveToDiskChanged += IntellisenseOptionsPageSaveToDiskChanged; + NodejsPackage.Instance.GeneralOptionsPage.ShowBrowserAndNodeLabelsChanged += ShowBrowserAndNodeLabelsChanged; + + return base.InitializeForOuter(filename, location, name, flags, ref iid, out projectPointer, out canceled); + } + + protected override void Reload() { + using (new DebugTimer("Project Load")) { + // Populate values from project properties before we do anything else. + // Otherwise we run into race conditions where, for instance, _analysisIgnoredDirectories + // is not properly set before the FileNodes get created in base.Reload() + UpdateProjectNodeFromProjectProperties(); + + if (_analyzer != null && _analyzer.RemoveUser()) { + _analyzer.Dispose(); + } + _analyzer = new VsProjectAnalyzer(ProjectFolder); + _analyzer.MaxLogLength = NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMax; + LogAnalysisLevel(); + + base.Reload(); + + SyncFileSystem(); + + NodejsPackage.Instance.CheckSurveyNews(false); + ModulesNode.ReloadHierarchySafe(); + + // scan for files which were loaded from cached analysis but no longer + // exist and remove them. + _analyzer.ReloadComplete(); + } + } + + private void UpdateProjectNodeFromProjectProperties() { + _intermediateOutputPath = Path.Combine(ProjectHome, GetProjectProperty("BaseIntermediateOutputPath")); + + var ignoredPaths = GetProjectProperty(NodejsConstants.AnalysisIgnoredDirectories); + + if (!string.IsNullOrWhiteSpace(ignoredPaths)) { + _analysisIgnoredDirs = _analysisIgnoredDirs.Append(ignoredPaths.Split(';').Select(x => '\\' + x + '\\').ToArray()); + } + + var maxFileSizeProp = GetProjectProperty(NodejsConstants.AnalysisMaxFileSize); + int maxFileSize; + if (maxFileSizeProp != null && Int32.TryParse(maxFileSizeProp, out maxFileSize)) { + _maxFileSize = maxFileSize; + } + } + + private void Reanalyze(HierarchyNode node, VsProjectAnalyzer newAnalyzer) { + if (node != null) { + for (var child = node.FirstChild; child != null; child = child.NextSibling) { + if (child is PackageJsonFileNode) { + ((PackageJsonFileNode)child).AnalyzePackageJson(newAnalyzer); + } else if (child is NodejsFileNode) { + if (((NodejsFileNode)child).ShouldAnalyze) { + newAnalyzer.AnalyzeFile(child.Url, !child.IsNonMemberItem); + } + } + + Reanalyze(child, newAnalyzer); + } + } + } + + private void LogAnalysisLevel() { + var analyzer = _analyzer; + if (analyzer != null) { + NodejsPackage.Instance.Logger.LogEvent(Logging.NodejsToolsLogEvent.AnalysisLevel, (int)analyzer.AnalysisLevel); + } + } + + /* + * Needed if we switch to per project Analysis levels + internal NodejsTools.Options.AnalysisLevel AnalysisLevel(){ + var analyzer = _analyzer; + if (_analyzer != null) { + return _analyzer.AnalysisLevel; + } + return NodejsTools.Options.AnalysisLevel.None; + } + */ + private void IntellisenseOptionsPageAnalysisLevelChanged(object sender, EventArgs e) { + + var oldAnalyzer = _analyzer; + _analyzer = null; + + var analyzer = new VsProjectAnalyzer(ProjectFolder); + Reanalyze(this, analyzer); + if (oldAnalyzer != null) { + analyzer.SwitchAnalyzers(oldAnalyzer); + if (oldAnalyzer.RemoveUser()) { + oldAnalyzer.Dispose(); + } + } + _analyzer = analyzer; + _analyzer.MaxLogLength = NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMax; + LogAnalysisLevel(); + } + + private void AnalysisLogMaximumChanged(object sender, EventArgs e) { + if (_analyzer != null) { + _analyzer.MaxLogLength = NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMax; + } + } + + private void IntellisenseOptionsPageSaveToDiskChanged(object sender, EventArgs e) { + if (_analyzer != null) { + _analyzer.SaveToDisk = NodejsPackage.Instance.IntellisenseOptionsPage.SaveToDisk; + } + } + + private void ShowBrowserAndNodeLabelsChanged(object sender, EventArgs e) { + var nodejsFolderNodes = this.AllDescendants.Where(item => (item as NodejsFolderNode) != null).Select(item => (NodejsFolderNode)item); + foreach (var node in nodejsFolderNodes) { + ProjectMgr.ReDrawNode(node, UIHierarchyElement.Caption); + } + } + + protected override void RaiseProjectPropertyChanged(string propertyName, string oldValue, string newValue) { + base.RaiseProjectPropertyChanged(propertyName, oldValue, newValue); + + var propPage = GeneralPropertyPageControl; + if (propPage != null) { + switch (propertyName) { + case NodejsConstants.Environment: + propPage.Environment = newValue; + break; + case NodejsConstants.DebuggerPort: + propPage.DebuggerPort = newValue; + break; + case NodejsConstants.NodejsPort: + propPage.NodejsPort = newValue; + break; + case NodejsConstants.NodeExePath: + propPage.NodeExePath = newValue; + break; + case NodejsConstants.NodeExeArguments: + propPage.NodeExeArguments = newValue; + break; + case CommonConstants.StartupFile: + propPage.ScriptFile = newValue; + break; + case NodejsConstants.ScriptArguments: + propPage.ScriptArguments = newValue; + break; + case NodejsConstants.LaunchUrl: + propPage.LaunchUrl = newValue; + break; + case NodejsConstants.StartWebBrowser: + bool value; + if (Boolean.TryParse(newValue, out value)) { + propPage.StartWebBrowser = value; + } + break; + case CommonConstants.WorkingDirectory: + propPage.WorkingDirectory = newValue; + break; + default: + if (propPage != null) { + PropertyPage.IsDirty = true; + } + break; + } + } + } + + private NodejsGeneralPropertyPageControl GeneralPropertyPageControl { + get { + if (PropertyPage != null && PropertyPage.Control != null) { + return (NodejsGeneralPropertyPageControl)PropertyPage.Control; + } + + return null; + } + } + + private static void AddFolderForFile(Dictionary> directoryPackages, FileNode rootFile, CommonFolderNode folderChild) { + List folders; + if (!directoryPackages.TryGetValue(rootFile, out folders)) { + directoryPackages[rootFile] = folders = new List(); + } + folders.Add(folderChild); + } + + protected override bool IncludeNonMemberItemInProject(HierarchyNode node) { + var fileNode = node as NodejsFileNode; + if (fileNode != null) { + return IncludeNodejsFile(fileNode); + } + return false; + } + + internal bool IncludeNodejsFile(NodejsFileNode fileNode) { + var url = fileNode.Url; + if (CommonUtils.IsSubpathOf(_intermediateOutputPath, fileNode.Url)) { + return false; + } + + foreach (var path in _analysisIgnoredDirs) { + if (url.IndexOf(path, 0, StringComparison.OrdinalIgnoreCase) != -1) { + return false; + } + } + + var fileInfo = new FileInfo(fileNode.Url); + if (!fileInfo.Exists || fileInfo.Length > _maxFileSize) { + // skip obviously generated and missing files... + return false; + } + + int nestedModulesDepth = 0; + if (ModulesNode.NpmController.RootPackage != null && ModulesNode.NpmController.RootPackage.Modules != null) { + nestedModulesDepth = ModulesNode.NpmController.RootPackage.Modules.GetDepth(fileNode.Url); + } + + if (_analyzer != null && _analyzer.Project != null && + _analyzer.Project.Limits.IsPathExceedNestingLimit(nestedModulesDepth)) { + return false; + } + + return true; + } + + internal override object Object { + get { + return this; + } + } + + protected override ReferenceContainerNode CreateReferenceContainerNode() { + return null; + } + + public NodeModulesNode ModulesNode { get; private set; } + + + + protected internal override void ProcessReferences() { + base.ProcessReferences(); + + if (null == ModulesNode) { + ModulesNode = new NodeModulesNode(this); + AddChild(ModulesNode); + _idleNodeModulesTimer = new Timer(OnIdleNodeModules); + } + } + + #region VSWebSite Members + + // This interface is just implemented so we don't get normal profiling which + // doesn't work with our projects anyway. + + public EnvDTE.ProjectItem AddFromTemplate(string bstrRelFolderUrl, string bstrWizardName, string bstrLanguage, string bstrItemName, bool bUseCodeSeparation, string bstrMasterPage, string bstrDocType) { + throw new NotImplementedException(); + } + + public VsWebSite.CodeFolders CodeFolders { + get { throw new NotImplementedException(); } + } + + public EnvDTE.DTE DTE { + get { return Project.DTE; } + } + + public string EnsureServerRunning() { + throw new NotImplementedException(); + } + + public string GetUniqueFilename(string bstrFolder, string bstrRoot, string bstrDesiredExt) { + throw new NotImplementedException(); + } + + public bool PreCompileWeb(string bstrCompilePath, bool bUpdateable) { + throw new NotImplementedException(); + } + + public EnvDTE.Project Project { + get { return (OAProject)GetAutomationObject(); } + } + + public VsWebSite.AssemblyReferences References { + get { throw new NotImplementedException(); } + } + + public void Refresh() { + } + + public string TemplatePath { + get { throw new NotImplementedException(); } + } + + public string URL { + get { throw new NotImplementedException(); } + } + + public string UserTemplatePath { + get { throw new NotImplementedException(); } + } + + public VsWebSite.VSWebSiteEvents VSWebSiteEvents { + get { throw new NotImplementedException(); } + } + + public void WaitUntilReady() { + } + + public VsWebSite.WebReferences WebReferences { + get { throw new NotImplementedException(); } + } + + public VsWebSite.WebServices WebServices { + get { throw new NotImplementedException(); } + } + + #endregion + + Task INodePackageModulesCommands.InstallMissingModulesAsync() { + //Fire off the command to update the missing modules + // through NPM + return ModulesNode.InstallMissingModules(); + } + + private void HookErrorsAndWarnings(VsProjectAnalyzer res) { + res.ErrorAdded += OnErrorAdded; + res.ErrorRemoved += OnErrorRemoved; + res.WarningAdded += OnWarningAdded; + res.WarningRemoved += OnWarningRemoved; + } + + private void UnHookErrorsAndWarnings(VsProjectAnalyzer res) { + res.ErrorAdded -= OnErrorAdded; + res.ErrorRemoved -= OnErrorRemoved; + res.WarningAdded -= OnWarningAdded; + res.WarningRemoved -= OnWarningRemoved; + } + + private void OnErrorAdded(object sender, FileEventArgs args) { + if (_diskNodes.ContainsKey(args.Filename)) { + _errorFiles.Add(args.Filename); + } + } + + private void OnErrorRemoved(object sender, FileEventArgs args) { + _errorFiles.Remove(args.Filename); + } + + private void OnWarningAdded(object sender, FileEventArgs args) { + if (_diskNodes.ContainsKey(args.Filename)) { + _warningFiles.Add(args.Filename); + } + } + + private void OnWarningRemoved(object sender, FileEventArgs args) { + _warningFiles.Remove(args.Filename); + } + + /// + /// File names within the project which contain errors. + /// + public HashSet ErrorFiles { + get { + return _errorFiles; + } + } + + /// + /// File names within the project which contain warnings. + /// + public HashSet WarningFiles { + get { + return _warningFiles; + } + } + + internal struct LongPathInfo { + public string FullPath; + public string RelativePath; + public bool IsDirectory; + } + + private static readonly Regex _uninstallRegex = new Regex(@"\b(uninstall|rm)\b"); + private static readonly char[] _pathSeparators = { '\\', '/' }; + private bool _isCheckingForLongPaths; + + public async Task CheckForLongPaths(string npmArguments = null) { + if (_isCheckingForLongPaths || !NodejsPackage.Instance.GeneralOptionsPage.CheckForLongPaths) { + return; + } + + if (npmArguments != null && _uninstallRegex.IsMatch(npmArguments)) { + return; + } + + try { + _isCheckingForLongPaths = true; + TaskDialogButton dedupeButton, ignoreButton, disableButton; + var taskDialog = new TaskDialog(NodejsPackage.Instance) { + AllowCancellation = true, + EnableHyperlinks = true, + Title = SR.GetString(SR.LongPathWarningTitle), + MainIcon = TaskDialogIcon.Warning, + Content = SR.GetString(SR.LongPathWarningText), + CollapsedControlText = SR.GetString(SR.LongPathShowPathsExceedingTheLimit), + ExpandedControlText = SR.GetString(SR.LongPathHidePathsExceedingTheLimit), + Buttons = { + (dedupeButton = new TaskDialogButton(SR.GetString(SR.LongPathNpmDedupe), SR.GetString(SR.LongPathNpmDedupeDetail))), + (ignoreButton = new TaskDialogButton(SR.GetString(SR.LongPathDoNothingButWarnNextTime))), + (disableButton = new TaskDialogButton(SR.GetString(SR.LongPathDoNothingAndDoNotWarnAgain), SR.GetString(SR.LongPathDoNothingAndDoNotWarnAgainDetail))) + }, + FooterIcon = TaskDialogIcon.Information, + Footer = SR.GetString(SR.LongPathFooter) + }; + + taskDialog.HyperlinkClicked += (sender, e) => { + switch (e.Url) { + case "#msdn": + Process.Start("http://go.microsoft.com/fwlink/?LinkId=454508"); + break; + case "#uservoice": + Process.Start("http://go.microsoft.com/fwlink/?LinkID=456509"); + break; + case "#help": + Process.Start("http://go.microsoft.com/fwlink/?LinkId=456511"); + break; + default: + System.Windows.Clipboard.SetText(e.Url); + break; + } + }; + + recheck: + + var longPaths = await Task.Factory.StartNew(() => + GetLongSubPaths(ProjectHome) + .Concat(GetLongSubPaths(_intermediateOutputPath)) + .Select(lpi => string.Format("• {1}\u00A0{2}", lpi.FullPath, lpi.RelativePath, SR.GetString(SR.LongPathClickToCopy))) + .ToArray()); + if (longPaths.Length == 0) { + return; + } + taskDialog.ExpandedInformation = string.Join("\r\n", longPaths); + + var button = taskDialog.ShowModal(); + if (button == dedupeButton) { + var repl = NodejsPackage.Instance.OpenReplWindow(focus: false); + await repl.ExecuteCommand(".npm dedupe").HandleAllExceptions(SR.ProductName); + + taskDialog.Content += "\r\n\r\n" + SR.GetString(SR.LongPathNpmDedupeDidNotHelp); + taskDialog.Buttons.Remove(dedupeButton); + goto recheck; + } else if (button == disableButton) { + var page = NodejsPackage.Instance.GeneralOptionsPage; + page.CheckForLongPaths = false; + page.SaveSettingsToStorage(); + } + } finally { + _isCheckingForLongPaths = false; + } + } + + internal static IEnumerable GetLongSubPaths(string basePath, string path = "") { + const int MaxFilePathLength = 260 - 1; // account for terminating NULL + const int MaxDirectoryPathLength = 248 - 1; + + basePath = CommonUtils.EnsureEndSeparator(basePath); + + WIN32_FIND_DATA wfd; + IntPtr hFind = NativeMethods.FindFirstFile(basePath + path + "\\*", out wfd); + if (hFind == NativeMethods.INVALID_HANDLE_VALUE) { + yield break; + } + + try { + do { + if (wfd.cFileName == "." || wfd.cFileName == "..") { + continue; + } + + bool isDirectory = (wfd.dwFileAttributes & NativeMethods.FILE_ATTRIBUTE_DIRECTORY) != 0; + + string childPath = path; + if (childPath != String.Empty) { + childPath += "\\"; + } + childPath += wfd.cFileName; + + string fullChildPath = basePath + childPath; + bool isTooLong; + try { + isTooLong = Path.GetFullPath(fullChildPath).Length > (isDirectory ? MaxDirectoryPathLength : MaxFilePathLength); + } catch (PathTooLongException) { + isTooLong = true; + } catch (Exception) { + continue; + } + + if (isTooLong) { + yield return new LongPathInfo { FullPath = fullChildPath, RelativePath = childPath, IsDirectory = isDirectory }; + } else if (isDirectory) { + foreach (var item in GetLongSubPaths(basePath, childPath)) { + yield return item; + } + } + } while (NativeMethods.FindNextFile(hFind, out wfd)); + } finally { + NativeMethods.FindClose(hFind); + } + } + + protected override void Dispose(bool disposing) { + if (disposing) { + if (_analyzer != null) { + UnHookErrorsAndWarnings(_analyzer); + if (WarningFiles.Count > 0 || ErrorFiles.Count > 0) { + foreach (var file in WarningFiles.Concat(ErrorFiles)) { + var node = FindNodeByFullPath(file) as NodejsFileNode; + if (node != null) { + //_analyzer.RemoveErrors(node.GetAnalysis(), suppressUpdate: false); + } + } + } + + if (_analyzer.RemoveUser()) { + _analyzer.Dispose(); + } + _analyzer = null; + } + + lock (_idleNodeModulesLock) { + if (_idleNodeModulesTimer != null) { + _idleNodeModulesTimer.Dispose(); + } + _idleNodeModulesTimer = null; + } + + NodejsPackage.Instance.IntellisenseOptionsPage.SaveToDiskChanged -= IntellisenseOptionsPageSaveToDiskChanged; + NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLevelChanged -= IntellisenseOptionsPageAnalysisLevelChanged; + NodejsPackage.Instance.IntellisenseOptionsPage.AnalysisLogMaximumChanged -= AnalysisLogMaximumChanged; + } + base.Dispose(disposing); + } + + internal override async void BuildAsync(uint vsopts, string config, VisualStudio.Shell.Interop.IVsOutputWindowPane output, string target, Action uiThreadCallback) { + try { + await CheckForLongPaths(); + } catch (Exception) { + uiThreadCallback(MSBuildResult.Failed, target); + return; + } + + // BuildAsync can throw on the sync path before invoking the callback. If it does, we must still invoke the callback here, + // because by this time there's no other way to propagate the error to the caller. + try { + base.BuildAsync(vsopts, config, output, target, uiThreadCallback); + } catch (Exception) { + uiThreadCallback(MSBuildResult.Failed, target); + } + } + + internal override int QueryStatusOnNode(Guid cmdGroup, uint cmd, IntPtr pCmdText, ref QueryStatusResult result) { + if (cmdGroup == Guids.NodejsCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidOpenReplWindow: + result = QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; + return VSConstants.S_OK; + } + } + return base.QueryStatusOnNode(cmdGroup, cmd, pCmdText, ref result); + } + + protected override QueryStatusResult QueryStatusSelectionOnNodes(IList selectedNodes, Guid cmdGroup, uint cmd, IntPtr pCmdText) { + if (cmdGroup == Guids.NodejsNpmCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidNpmManageModules: + if (IsCurrentStateASuppressCommandsMode()) { + return QueryStatusResult.SUPPORTED; + } else if (!ShowManageModulesCommandOnNode(selectedNodes)) { + return QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED | QueryStatusResult.INVISIBLE; + } + return QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; + } + } else if (cmdGroup == Guids.NodejsCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidSetAsNodejsStartupFile: + if (ShowSetAsStartupFileCommandOnNode(selectedNodes)) { + // We enable "Set as StartUp File" command only on current language code files, + // the file is in project home dir and if the file is not the startup file already. + string startupFile = ((CommonProjectNode)ProjectMgr).GetStartupFile(); + if (!CommonUtils.IsSamePath(startupFile, selectedNodes[0].Url)) { + return QueryStatusResult.SUPPORTED | QueryStatusResult.ENABLED; + } + } + break; + } + } + + return base.QueryStatusSelectionOnNodes(selectedNodes, cmdGroup, cmd, pCmdText); + } + + internal override int ExecCommandOnNode(Guid cmdGroup, uint cmd, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + if (cmdGroup == Guids.NodejsCmdSet) { + switch (cmd) { + case PkgCmdId.cmdidOpenReplWindow: + NodejsPackage.Instance.OpenReplWindow(); + return VSConstants.S_OK; + } + } else if (cmdGroup == Guids.NodejsNpmCmdSet) { + try { + NpmHelpers.GetPathToNpm( + Nodejs.GetAbsoluteNodeExePath( + ProjectHome, + Project.GetNodejsProject().GetProjectProperty(NodejsConstants.NodeExePath) + )); + } catch (NpmNotFoundException) { + Nodejs.ShowNodejsNotInstalled(); + return VSConstants.S_OK; + } + } + return base.ExecCommandOnNode(cmdGroup, cmd, nCmdexecopt, pvaIn, pvaOut); + } + + protected override int ExecCommandThatDependsOnSelectedNodes(Guid cmdGroup, uint cmdId, uint cmdExecOpt, IntPtr vaIn, IntPtr vaOut, CommandOrigin commandOrigin, IList selectedNodes, out bool handled) { + if (cmdGroup == Guids.NodejsNpmCmdSet) { + try { + NpmHelpers.GetPathToNpm( + Nodejs.GetAbsoluteNodeExePath( + ProjectHome, + Project.GetNodejsProject().GetProjectProperty(NodejsConstants.NodeExePath) + )); + } catch (NpmNotFoundException) { + Nodejs.ShowNodejsNotInstalled(); + handled = true; + return VSConstants.S_OK; + } + + switch (cmdId) { + case PkgCmdId.cmdidNpmManageModules: + if (!ShowManageModulesCommandOnNode(selectedNodes)) { + ModulesNode.ManageModules(); + handled = true; + return VSConstants.S_OK; + } + + var node = selectedNodes[0] as AbstractNpmNode; + if (node != null) { + var abstractNpmNode = node; + abstractNpmNode.ManageNpmModules(); + handled = true; + return VSConstants.S_OK; + } + break; + } + } else if (cmdGroup == Guids.NodejsCmdSet) { + switch (cmdId) { + case PkgCmdId.cmdidSetAsNodejsStartupFile: + // Set the StartupFile project property to the Url of this node + SetProjectProperty( + CommonConstants.StartupFile, + CommonUtils.GetRelativeFilePath(ProjectHome, selectedNodes[0].Url) + ); + handled = true; + return VSConstants.S_OK; + } + } + + return base.ExecCommandThatDependsOnSelectedNodes(cmdGroup, cmdId, cmdExecOpt, vaIn, vaOut, commandOrigin, selectedNodes, out handled); + } + + private bool ShowSetAsStartupFileCommandOnNode(IList selectedNodes) { + var selectedNodeUrl = selectedNodes[0].Url; + return selectedNodes.Count == 1 && + (IsCodeFile(selectedNodeUrl) || + // for some reason, the default express 4 template's startup file lacks an extension. + string.IsNullOrEmpty(Path.GetExtension(selectedNodeUrl))); + } + + private static bool ShowManageModulesCommandOnNode(IList selectedNodes) { + return selectedNodes.Count == 1 && selectedNodes[0] is AbstractNpmNode; + } + + protected internal override void SetCurrentConfiguration() { + if (!IsProjectOpened) { + return; + } + + if (this.IsPlatformAware()) { + EnvDTE.Project automationObject = GetAutomationObject() as EnvDTE.Project; + + this.BuildProject.SetGlobalProperty(ProjectFileConstants.Platform, automationObject.ConfigurationManager.ActiveConfiguration.PlatformName); + } + base.SetCurrentConfiguration(); + } + + public override MSBuildResult Build(string config, string target) { + if (this.IsPlatformAware()) { + var platform = this.BuildProject.GetPropertyValue(GlobalProperty.Platform.ToString()); + + if (platform == ProjectConfig.AnyCPU) { + this.BuildProject.SetGlobalProperty(GlobalProperty.Platform.ToString(), ConfigProvider.x86Platform); + } + } + return base.Build(config, target); + } + + } +} diff --git a/Nodejs/Product/Nodejs/Project/NodejsProjectNodeProperties.cs b/Nodejs/Product/Nodejs/Project/NodejsProjectNodeProperties.cs index 54787d697..d078e1264 100644 --- a/Nodejs/Product/Nodejs/Project/NodejsProjectNodeProperties.cs +++ b/Nodejs/Product/Nodejs/Project/NodejsProjectNodeProperties.cs @@ -60,8 +60,8 @@ public uint TargetFramework { public string NodeExePath { get { return HierarchyNode.ProjectMgr.Site.GetUIThread().Invoke(() => { - return Nodejs.GetAbsoluteNodeExePath( - ProjectHome, + return Nodejs.GetAbsoluteNodeExePath( + ProjectHome, Node.ProjectMgr.GetProjectProperty(NodejsConstants.NodeExePath, true)); }); } diff --git a/Nodejs/Product/Nodejs/Project/NpmNodeProperties.cs b/Nodejs/Product/Nodejs/Project/NpmNodeProperties.cs index 557696514..4508ae7f1 100644 --- a/Nodejs/Product/Nodejs/Project/NpmNodeProperties.cs +++ b/Nodejs/Product/Nodejs/Project/NpmNodeProperties.cs @@ -1,80 +1,80 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading.Tasks; -using Microsoft.VisualStudioTools.Project; - -namespace Microsoft.NodejsTools.Project { - [ComVisible(true)] - [ClassInterface(ClassInterfaceType.AutoDual)] - [Guid("3C3BD073-2AB3-4E66-BBE9-C8B2D8A774D1")] - public class NpmNodeProperties : NodeProperties { - internal NpmNodeProperties(AbstractNpmNode node) : base(node) {} - - private AbstractNpmNode NpmNode { - get { return Node as AbstractNpmNode; } - } - - private bool IsGlobalNode { - get { return NpmNode is GlobalModulesNode; } - } - - public override string GetClassName() { - return SR.GetString(IsGlobalNode ? SR.PropertiesClassGlobal : SR.PropertiesClassNpm); - } - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmNodePackageInstallation)] - [SRDescriptionAttribute(SR.NpmNodePackageInstallationDescription)] - public string PackageInstallation { - get { - return SR.GetString(IsGlobalNode ? SR.PackageInstallationGlobal : SR.PackageInstallationLocal); - } - } - - [SRCategoryAttribute(SR.General)] - [SRDisplayName(SR.NpmNodePath)] - [SRDescriptionAttribute(SR.NpmNodePathDescription)] - public string Path { - get { - var node = NpmNode; - if (null != node) { - var local = node as NodeModulesNode; - if (null != local) { - var root = local.RootPackage; - if (null != root) { - return root.Path; - } - } else { - var glob = node as GlobalModulesNode; - if (null != glob) { - var packages = glob.GlobalPackages; - if (null != packages) { - return packages.Path; - } - } - } - } - return null; - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using Microsoft.VisualStudioTools.Project; + +namespace Microsoft.NodejsTools.Project { + [ComVisible(true)] + [ClassInterface(ClassInterfaceType.AutoDual)] + [Guid("3C3BD073-2AB3-4E66-BBE9-C8B2D8A774D1")] + public class NpmNodeProperties : NodeProperties { + internal NpmNodeProperties(AbstractNpmNode node) : base(node) {} + + private AbstractNpmNode NpmNode { + get { return Node as AbstractNpmNode; } + } + + private bool IsGlobalNode { + get { return NpmNode is GlobalModulesNode; } + } + + public override string GetClassName() { + return SR.GetString(IsGlobalNode ? SR.PropertiesClassGlobal : SR.PropertiesClassNpm); + } + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmNodePackageInstallation)] + [SRDescriptionAttribute(SR.NpmNodePackageInstallationDescription)] + public string PackageInstallation { + get { + return SR.GetString(IsGlobalNode ? SR.PackageInstallationGlobal : SR.PackageInstallationLocal); + } + } + + [SRCategoryAttribute(SR.General)] + [SRDisplayName(SR.NpmNodePath)] + [SRDescriptionAttribute(SR.NpmNodePathDescription)] + public string Path { + get { + var node = NpmNode; + if (null != node) { + var local = node as NodeModulesNode; + if (null != local) { + var root = local.RootPackage; + if (null != root) { + return root.Path; + } + } else { + var glob = node as GlobalModulesNode; + if (null != glob) { + var packages = glob.GlobalPackages; + if (null != packages) { + return packages.Path; + } + } + } + } + return null; + } + } + } +} diff --git a/Nodejs/Product/Nodejs/Project/ProjectResources.cs b/Nodejs/Product/Nodejs/Project/ProjectResources.cs index 3b6b18257..3d3b3ec9f 100644 --- a/Nodejs/Product/Nodejs/Project/ProjectResources.cs +++ b/Nodejs/Product/Nodejs/Project/ProjectResources.cs @@ -1,205 +1,205 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Resources; -using System.Threading; -using CommonSR = Microsoft.VisualStudioTools.Project.SR; - -namespace Microsoft.NodejsTools.Project { - internal class SR : CommonSR { - internal const string NodejsToolsForVisualStudio = "NodejsToolsForVisualStudio"; - - internal const string AzureToolsInstallInstructions = "AzureToolsInstallInstructions"; - internal const string AzureToolsRequired = "AzureToolsRequired"; - internal const string AzureToolsUpgradeInstructions = "AzureToolsUpgradeInstructions"; - internal const string AzureToolsUpgradeRecommended = "AzureToolsUpgradeRecommended"; - internal const string CatalogLoadingDefault = "CatalogLoadingDefault"; - internal const string CatalogLoadingNoNpm = "CatalogLoadingNoNpm"; - internal const string CategoryStatus = "CategoryStatus"; - internal const string CategoryVersion = "CategoryVersion"; - internal const string ContinueWithoutAzureToolsUpgrade = "ContinueWithoutAzureToolsUpgrade"; - internal const string DebuggerConnectionClosed = "DebuggerConnectionClosed"; - internal const string DebuggerModuleUpdateFailed = "DebuggerModuleUpdateFailed"; - internal const string DebuggerPort = "DebuggerPort"; - internal const string DontShowAgain = "DontShowAgain"; - internal const string DownloadAndInstall = "DownloadAndInstall"; - internal const string EnvironmentVariables = "EnvironmentVariables"; - internal const string ErrorNoDte = "ErrorNoDte"; - internal const string IncludeNodeModulesCancelTitle = "IncludeNodeModulesCancelTitle"; - internal const string IncludeNodeModulesContent = "IncludeNodeModulesContent"; - internal const string IncludeNodeModulesIncludeDescription = "IncludeNodeModulesIncludeDescription"; - internal const string IncludeNodeModulesIncludeTitle = "IncludeNodeModulesIncludeTitle"; - internal const string IncludeNodeModulesInformation = "IncludeNodeModulesInformation"; - internal const string InsertSnippet = "InsertSnippet"; - internal const string InvalidPortNumber = "InvalidPortNumber"; - internal const string LaunchUrlToolTip = "LaunchUrlToolTip"; - internal const string LinkStatusLinkedFromGlobal = "LinkStatusLinkedFromGlobal"; - internal const string LinkStatusLinkedToProject = "LinkStatusLinkedToProject"; - internal const string LinkStatusLocallyInstalled = "LinkStatusLocallyInstalled"; - internal const string LinkStatusNotApplicableSubPackages = "LinkStatusNotApplicableSubPackages"; - internal const string LinkStatusNotLinkedToProject = "LinkStatusNotLinkedToProject"; - internal const string LinkStatusUnknown = "LinkStatusUnknown"; - internal const string LongPathClickToCopy = "LongPathClickToCopy"; - internal const string LongPathDoNothingAndDoNotWarnAgain = "LongPathDoNothingAndDoNotWarnAgain"; - internal const string LongPathDoNothingAndDoNotWarnAgainDetail = "LongPathDoNothingAndDoNotWarnAgainDetail"; - internal const string LongPathDoNothingButWarnNextTime = "LongPathDoNothingButWarnNextTime"; - internal const string LongPathFooter = "LongPathFooter"; - internal const string LongPathHidePathsExceedingTheLimit = "LongPathHidePathsExceedingTheLimit"; - internal const string LongPathNpmDedupe = "LongPathNpmDedupe"; - internal const string LongPathNpmDedupeDetail = "LongPathNpmDedupeDetail"; - internal const string LongPathNpmDedupeDidNotHelp = "LongPathNpmDedupeDidNotHelp"; - internal const string LongPathShowPathsExceedingTheLimit = "LongPathShowPathsExceedingTheLimit"; - internal const string LongPathWarningText = "LongPathWarningText"; - internal const string LongPathWarningTitle = "LongPathWarningTitle"; - internal const string Milliseconds = "Milliseconds"; - internal const string NewVersionNo = "NewVersionNo"; - internal const string NewVersionNotApplicableSubpackage = "NewVersionNotApplicableSubpackage"; - internal const string NewVersionPackageCatalogNotRetrieved = "NewVersionPackageCatalogNotRetrieved"; - internal const string NewVersionUnknown = "NewVersionUnknown"; - internal const string NewVersionYes = "NewVersionYes"; - internal const string NodeExeArguments = "NodeExeArguments"; - internal const string NodeExeArgumentsDescription = "NodeExeArgumentsDescription"; - internal const string NodeExeArgumentsToolTip = "NodeExeArgumentsToolTip"; - internal const string NodeExeDoesntExist = "NodeExeDoesntExist"; - internal const string NodeExePath = "NodeExePath"; - internal const string NodeExePathDescription = "NodeExePathDescription"; - internal const string NodeExePathNotFound = "NodeExePathNotFound"; - internal const string NodeExePathToolTip = "NodeExePathToolTip"; - internal const string NodejsNotInstalled = "NodejsNotInstalled"; - internal const string NodejsNotInstalledShort = "NodejsNotInstalledShort"; - internal const string NodejsNotSupported = "NodejsNotSupported"; - internal const string NodejsPort = "NodejsPort"; - internal const string NodejsPortDescription = "NodejsPortDescription"; - internal const string NodejsPortToolTip = "NodejsPortToolTip"; - internal const string NoKeywordsInPackage = "NoKeywordsInPackage"; - internal const string NpmCancelled = "NpmCancelled"; - internal const string NpmCancelledWithErrors = "NpmCancelledWithErrors"; - internal const string NpmCompletedWithErrors = "NpmCompletedWithErrors"; - internal const string NpmNodePackageInstallation = "NpmNodePackageInstallation"; - internal const string NpmNodePackageInstallationDescription = "NpmNodePackageInstallationDescription"; - internal const string NpmNodePath = "NpmNodePath"; - internal const string NpmNodePathDescription = "NpmNodePathDescription"; - internal const string NpmOutputPaneTitle = "NpmOutputPaneTitle"; - internal const string NpmPackageAuthor = "NpmPackageAuthor"; - internal const string NpmPackageAuthorDescription = "NpmPackageAuthorDescription"; - internal const string NpmPackageDescription = "NpmPackageDescription"; - internal const string NpmPackageDescriptionDescription = "NpmPackageDescriptionDescription"; - internal const string NpmPackageInstallHelpMessage = "NpmPackageInstallHelpMessage"; - internal const string NpmPackageIsBundledDependency = "NpmPackageIsBundledDependency"; - internal const string NpmPackageIsBundledDependencyDescription = "NpmPackageIsBundledDependencyDescription"; - internal const string NpmPackageIsDevDependency = "NpmPackageIsDevDependency"; - internal const string NpmPackageIsDevDependencyDescription = "NpmPackageIsDevDependencyDescription"; - internal const string NpmPackageIsListedInParentPackageJson = "NpmPackageIsListedInParentPackageJson"; - internal const string NpmPackageIsListedInParentPackageJsonDescription = "NpmPackageIsListedInParentPackageJsonDescription"; - internal const string NpmPackageIsMissing = "NpmPackageIsMissing"; - internal const string NpmPackageIsMissingDescription = "NpmPackageIsMissingDescription"; - internal const string NpmPackageIsOptionalDependency = "NpmPackageIsOptionalDependency"; - internal const string NpmPackageIsOptionalDependencyDescription = "NpmPackageIsOptionalDependencyDescription"; - internal const string NpmPackageKeywords = "NpmPackageKeywords"; - internal const string NpmPackageKeywordsDescription = "NpmPackageKeywordsDescription"; - internal const string NpmPackageLinkStatus = "NpmPackageLinkStatus"; - internal const string NpmPackageLinkStatusDescription = "NpmPackageLinkStatusDescription"; - internal const string NpmPackageName = "NpmPackageName"; - internal const string NpmPackageNameDescription = "NpmPackageNameDescription"; - internal const string NpmPackageNewVersionAvailable = "NpmPackageNewVersionAvailable"; - internal const string NpmPackageNewVersionAvailableDescription = "NpmPackageNewVersionAvailableDescription"; - internal const string NpmPackagePath = "NpmPackagePath"; - internal const string NpmPackagePathDescription = "NpmPackagePathDescription"; - internal const string NpmPackageRequestedVersionRange = "NpmPackageRequestedVersionRange"; - internal const string NpmPackageRequestedVersionRangeDescription = "NpmPackageRequestedVersionRangeDescription"; - internal const string NpmPackageType = "NpmPackageType"; - internal const string NpmPackageTypeDescription = "NpmPackageTypeDescription"; - internal const string NpmPackageVersion = "NpmPackageVersion"; - internal const string NpmPackageVersionDescription = "NpmPackageVersionDescription"; - internal const string NpmReplCommandCompletedWithErrors = "NpmReplCommandCompletedWithErrors"; - internal const string NpmStatusExecuting = "NpmStatusExecuting"; - internal const string NpmStatusExecutingErrors = "NpmStatusExecutingErrors"; - internal const string NpmStatusExecutingQueued = "NpmStatusExecutingQueued"; - internal const string NpmStatusExecutingQueuedErrors = "NpmStatusExecutingQueuedErrors"; - internal const string NpmStatusReady = "NpmStatusReady"; - internal const string NpmStatusReadyWithErrors = "NpmStatusReadyWithErrors"; - internal const string NpmSuccessfullyCompleted = "NpmSuccessfullyCompleted"; - internal const string PackageCatalogRefresh0Days = "PackageCatalogRefresh0Days"; - internal const string PackageCatalogRefresh1Day = "PackageCatalogRefresh1Day"; - internal const string PackageCatalogRefresh1Month = "PackageCatalogRefresh1Month"; - internal const string PackageCatalogRefresh1Week = "PackageCatalogRefresh1Week"; - internal const string PackageCatalogRefresh2To7Days = "PackageCatalogRefresh2To7Days"; - internal const string PackageCatalogRefresh2Weeks = "PackageCatalogRefresh2Weeks"; - internal const string PackageCatalogRefresh3Months = "PackageCatalogRefresh3Months"; - internal const string PackageCatalogRefresh3Weeks = "PackageCatalogRefresh3Weeks"; - internal const string PackageCatalogRefresh6Months = "PackageCatalogRefresh6Months"; - internal const string PackageCatalogRefreshFailed = "PackageCatalogRefreshFailed"; - internal const string PackageCatalogRefreshing = "PackageCatalogRefreshing"; - internal const string PackageCount = "PackageCount"; - internal const string PackageInstallationGlobal = "PackageInstallationGlobal"; - internal const string PackageInstallationLocal = "PackageInstallationLocal"; - internal const string PackageInstalledGlobally = "PackageInstalledGlobally"; - internal const string PackageInstalledGloballyOldVersion = "PackageInstalledGloballyOldVersion"; - internal const string PackageInstalledLocally = "PackageInstalledLocally"; - internal const string PackageInstalledLocallyOldVersion = "PackageInstalledLocallyOldVersion"; - internal const string PackageMatchCount = "PackageMatchCount"; - internal const string PackageTypeGlobal = "PackageTypeGlobal"; - internal const string PackageTypeGlobalSubpackage = "PackageTypeGlobalSubpackage"; - internal const string PackageTypeLocal = "PackageTypeLocal"; - internal const string PackageTypeLocalSubpackage = "PackageTypeLocalSubpackage"; - internal const string PropertiesClassGlobal = "PropertiesClassGlobal"; - internal const string PropertiesClassGlobalPackage = "PropertiesClassGlobalPackage"; - internal const string PropertiesClassGlobalSubPackage = "PropertiesClassGlobalSubPackage"; - internal const string PropertiesClassLocalPackage = "PropertiesClassLocalPackage"; - internal const string PropertiesClassLocalSubPackage = "PropertiesClassLocalSubPackage"; - internal const string PropertiesClassNpm = "PropertiesClassNpm"; - internal const string ReplInitializationMessage = "ReplInitializationMessage"; - internal const string RequestedVersionRangeNone = "RequestedVersionRangeNone"; - internal const string ScriptArgumentsToolTip = "ScriptArgumentsToolTip"; - internal const string ScriptFileToolTip = "ScriptFileTooltip"; - internal const string Seconds = "Seconds"; - internal const string StartBrowserToolTip = "StartBrowserToolTip"; - internal const string StatusAnalysisLoaded = "StatusAnalysisLoaded"; - internal const string StatusAnalysisLoadFailed = "StatusAnalysisLoadFailed"; - internal const string StatusAnalysisLoading = "StatusAnalysisLoading"; - internal const string StatusAnalysisSaved = "StatusAnalysisSaved"; - internal const string StatusAnalysisSaving = "StatusAnalysisSaving"; - internal const string StatusAnalysisUpToDate = "StatusAnalysisUpToDate"; - internal const string SurroundWith = "SurroundWith"; - internal const string TestFramework = "TestFramework"; - internal const string TestFrameworkDescription = "TestFrameworkDescription"; - internal const string UpgradedEnvironmentVariables = "UpgradedEnvironmentVariables"; - internal const string WorkingDirInvalidOrMissing = "WorkingDirInvalidOrMissing"; - internal const string WorkingDirToolTip = "WorkingDirToolTip"; - - private static readonly Lazy _manager = new Lazy( - () => new System.Resources.ResourceManager("Microsoft.NodejsTools.Resources", typeof(SR).Assembly), - LazyThreadSafetyMode.ExecutionAndPublication - ); - - private static ResourceManager Manager { - get { - return _manager.Value; - } - } - - internal static new string GetString(string value, params object[] args) { - return GetStringInternal(Manager, value, args) ?? CommonSR.GetString(value, args); - } - - internal static string ProductName { - get { - return GetString(NodejsToolsForVisualStudio); - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Resources; +using System.Threading; +using CommonSR = Microsoft.VisualStudioTools.Project.SR; + +namespace Microsoft.NodejsTools.Project { + internal class SR : CommonSR { + internal const string NodejsToolsForVisualStudio = "NodejsToolsForVisualStudio"; + + internal const string AzureToolsInstallInstructions = "AzureToolsInstallInstructions"; + internal const string AzureToolsRequired = "AzureToolsRequired"; + internal const string AzureToolsUpgradeInstructions = "AzureToolsUpgradeInstructions"; + internal const string AzureToolsUpgradeRecommended = "AzureToolsUpgradeRecommended"; + internal const string CatalogLoadingDefault = "CatalogLoadingDefault"; + internal const string CatalogLoadingNoNpm = "CatalogLoadingNoNpm"; + internal const string CategoryStatus = "CategoryStatus"; + internal const string CategoryVersion = "CategoryVersion"; + internal const string ContinueWithoutAzureToolsUpgrade = "ContinueWithoutAzureToolsUpgrade"; + internal const string DebuggerConnectionClosed = "DebuggerConnectionClosed"; + internal const string DebuggerModuleUpdateFailed = "DebuggerModuleUpdateFailed"; + internal const string DebuggerPort = "DebuggerPort"; + internal const string DontShowAgain = "DontShowAgain"; + internal const string DownloadAndInstall = "DownloadAndInstall"; + internal const string EnvironmentVariables = "EnvironmentVariables"; + internal const string ErrorNoDte = "ErrorNoDte"; + internal const string IncludeNodeModulesCancelTitle = "IncludeNodeModulesCancelTitle"; + internal const string IncludeNodeModulesContent = "IncludeNodeModulesContent"; + internal const string IncludeNodeModulesIncludeDescription = "IncludeNodeModulesIncludeDescription"; + internal const string IncludeNodeModulesIncludeTitle = "IncludeNodeModulesIncludeTitle"; + internal const string IncludeNodeModulesInformation = "IncludeNodeModulesInformation"; + internal const string InsertSnippet = "InsertSnippet"; + internal const string InvalidPortNumber = "InvalidPortNumber"; + internal const string LaunchUrlToolTip = "LaunchUrlToolTip"; + internal const string LinkStatusLinkedFromGlobal = "LinkStatusLinkedFromGlobal"; + internal const string LinkStatusLinkedToProject = "LinkStatusLinkedToProject"; + internal const string LinkStatusLocallyInstalled = "LinkStatusLocallyInstalled"; + internal const string LinkStatusNotApplicableSubPackages = "LinkStatusNotApplicableSubPackages"; + internal const string LinkStatusNotLinkedToProject = "LinkStatusNotLinkedToProject"; + internal const string LinkStatusUnknown = "LinkStatusUnknown"; + internal const string LongPathClickToCopy = "LongPathClickToCopy"; + internal const string LongPathDoNothingAndDoNotWarnAgain = "LongPathDoNothingAndDoNotWarnAgain"; + internal const string LongPathDoNothingAndDoNotWarnAgainDetail = "LongPathDoNothingAndDoNotWarnAgainDetail"; + internal const string LongPathDoNothingButWarnNextTime = "LongPathDoNothingButWarnNextTime"; + internal const string LongPathFooter = "LongPathFooter"; + internal const string LongPathHidePathsExceedingTheLimit = "LongPathHidePathsExceedingTheLimit"; + internal const string LongPathNpmDedupe = "LongPathNpmDedupe"; + internal const string LongPathNpmDedupeDetail = "LongPathNpmDedupeDetail"; + internal const string LongPathNpmDedupeDidNotHelp = "LongPathNpmDedupeDidNotHelp"; + internal const string LongPathShowPathsExceedingTheLimit = "LongPathShowPathsExceedingTheLimit"; + internal const string LongPathWarningText = "LongPathWarningText"; + internal const string LongPathWarningTitle = "LongPathWarningTitle"; + internal const string Milliseconds = "Milliseconds"; + internal const string NewVersionNo = "NewVersionNo"; + internal const string NewVersionNotApplicableSubpackage = "NewVersionNotApplicableSubpackage"; + internal const string NewVersionPackageCatalogNotRetrieved = "NewVersionPackageCatalogNotRetrieved"; + internal const string NewVersionUnknown = "NewVersionUnknown"; + internal const string NewVersionYes = "NewVersionYes"; + internal const string NodeExeArguments = "NodeExeArguments"; + internal const string NodeExeArgumentsDescription = "NodeExeArgumentsDescription"; + internal const string NodeExeArgumentsToolTip = "NodeExeArgumentsToolTip"; + internal const string NodeExeDoesntExist = "NodeExeDoesntExist"; + internal const string NodeExePath = "NodeExePath"; + internal const string NodeExePathDescription = "NodeExePathDescription"; + internal const string NodeExePathNotFound = "NodeExePathNotFound"; + internal const string NodeExePathToolTip = "NodeExePathToolTip"; + internal const string NodejsNotInstalled = "NodejsNotInstalled"; + internal const string NodejsNotInstalledShort = "NodejsNotInstalledShort"; + internal const string NodejsNotSupported = "NodejsNotSupported"; + internal const string NodejsPort = "NodejsPort"; + internal const string NodejsPortDescription = "NodejsPortDescription"; + internal const string NodejsPortToolTip = "NodejsPortToolTip"; + internal const string NoKeywordsInPackage = "NoKeywordsInPackage"; + internal const string NpmCancelled = "NpmCancelled"; + internal const string NpmCancelledWithErrors = "NpmCancelledWithErrors"; + internal const string NpmCompletedWithErrors = "NpmCompletedWithErrors"; + internal const string NpmNodePackageInstallation = "NpmNodePackageInstallation"; + internal const string NpmNodePackageInstallationDescription = "NpmNodePackageInstallationDescription"; + internal const string NpmNodePath = "NpmNodePath"; + internal const string NpmNodePathDescription = "NpmNodePathDescription"; + internal const string NpmOutputPaneTitle = "NpmOutputPaneTitle"; + internal const string NpmPackageAuthor = "NpmPackageAuthor"; + internal const string NpmPackageAuthorDescription = "NpmPackageAuthorDescription"; + internal const string NpmPackageDescription = "NpmPackageDescription"; + internal const string NpmPackageDescriptionDescription = "NpmPackageDescriptionDescription"; + internal const string NpmPackageInstallHelpMessage = "NpmPackageInstallHelpMessage"; + internal const string NpmPackageIsBundledDependency = "NpmPackageIsBundledDependency"; + internal const string NpmPackageIsBundledDependencyDescription = "NpmPackageIsBundledDependencyDescription"; + internal const string NpmPackageIsDevDependency = "NpmPackageIsDevDependency"; + internal const string NpmPackageIsDevDependencyDescription = "NpmPackageIsDevDependencyDescription"; + internal const string NpmPackageIsListedInParentPackageJson = "NpmPackageIsListedInParentPackageJson"; + internal const string NpmPackageIsListedInParentPackageJsonDescription = "NpmPackageIsListedInParentPackageJsonDescription"; + internal const string NpmPackageIsMissing = "NpmPackageIsMissing"; + internal const string NpmPackageIsMissingDescription = "NpmPackageIsMissingDescription"; + internal const string NpmPackageIsOptionalDependency = "NpmPackageIsOptionalDependency"; + internal const string NpmPackageIsOptionalDependencyDescription = "NpmPackageIsOptionalDependencyDescription"; + internal const string NpmPackageKeywords = "NpmPackageKeywords"; + internal const string NpmPackageKeywordsDescription = "NpmPackageKeywordsDescription"; + internal const string NpmPackageLinkStatus = "NpmPackageLinkStatus"; + internal const string NpmPackageLinkStatusDescription = "NpmPackageLinkStatusDescription"; + internal const string NpmPackageName = "NpmPackageName"; + internal const string NpmPackageNameDescription = "NpmPackageNameDescription"; + internal const string NpmPackageNewVersionAvailable = "NpmPackageNewVersionAvailable"; + internal const string NpmPackageNewVersionAvailableDescription = "NpmPackageNewVersionAvailableDescription"; + internal const string NpmPackagePath = "NpmPackagePath"; + internal const string NpmPackagePathDescription = "NpmPackagePathDescription"; + internal const string NpmPackageRequestedVersionRange = "NpmPackageRequestedVersionRange"; + internal const string NpmPackageRequestedVersionRangeDescription = "NpmPackageRequestedVersionRangeDescription"; + internal const string NpmPackageType = "NpmPackageType"; + internal const string NpmPackageTypeDescription = "NpmPackageTypeDescription"; + internal const string NpmPackageVersion = "NpmPackageVersion"; + internal const string NpmPackageVersionDescription = "NpmPackageVersionDescription"; + internal const string NpmReplCommandCompletedWithErrors = "NpmReplCommandCompletedWithErrors"; + internal const string NpmStatusExecuting = "NpmStatusExecuting"; + internal const string NpmStatusExecutingErrors = "NpmStatusExecutingErrors"; + internal const string NpmStatusExecutingQueued = "NpmStatusExecutingQueued"; + internal const string NpmStatusExecutingQueuedErrors = "NpmStatusExecutingQueuedErrors"; + internal const string NpmStatusReady = "NpmStatusReady"; + internal const string NpmStatusReadyWithErrors = "NpmStatusReadyWithErrors"; + internal const string NpmSuccessfullyCompleted = "NpmSuccessfullyCompleted"; + internal const string PackageCatalogRefresh0Days = "PackageCatalogRefresh0Days"; + internal const string PackageCatalogRefresh1Day = "PackageCatalogRefresh1Day"; + internal const string PackageCatalogRefresh1Month = "PackageCatalogRefresh1Month"; + internal const string PackageCatalogRefresh1Week = "PackageCatalogRefresh1Week"; + internal const string PackageCatalogRefresh2To7Days = "PackageCatalogRefresh2To7Days"; + internal const string PackageCatalogRefresh2Weeks = "PackageCatalogRefresh2Weeks"; + internal const string PackageCatalogRefresh3Months = "PackageCatalogRefresh3Months"; + internal const string PackageCatalogRefresh3Weeks = "PackageCatalogRefresh3Weeks"; + internal const string PackageCatalogRefresh6Months = "PackageCatalogRefresh6Months"; + internal const string PackageCatalogRefreshFailed = "PackageCatalogRefreshFailed"; + internal const string PackageCatalogRefreshing = "PackageCatalogRefreshing"; + internal const string PackageCount = "PackageCount"; + internal const string PackageInstallationGlobal = "PackageInstallationGlobal"; + internal const string PackageInstallationLocal = "PackageInstallationLocal"; + internal const string PackageInstalledGlobally = "PackageInstalledGlobally"; + internal const string PackageInstalledGloballyOldVersion = "PackageInstalledGloballyOldVersion"; + internal const string PackageInstalledLocally = "PackageInstalledLocally"; + internal const string PackageInstalledLocallyOldVersion = "PackageInstalledLocallyOldVersion"; + internal const string PackageMatchCount = "PackageMatchCount"; + internal const string PackageTypeGlobal = "PackageTypeGlobal"; + internal const string PackageTypeGlobalSubpackage = "PackageTypeGlobalSubpackage"; + internal const string PackageTypeLocal = "PackageTypeLocal"; + internal const string PackageTypeLocalSubpackage = "PackageTypeLocalSubpackage"; + internal const string PropertiesClassGlobal = "PropertiesClassGlobal"; + internal const string PropertiesClassGlobalPackage = "PropertiesClassGlobalPackage"; + internal const string PropertiesClassGlobalSubPackage = "PropertiesClassGlobalSubPackage"; + internal const string PropertiesClassLocalPackage = "PropertiesClassLocalPackage"; + internal const string PropertiesClassLocalSubPackage = "PropertiesClassLocalSubPackage"; + internal const string PropertiesClassNpm = "PropertiesClassNpm"; + internal const string ReplInitializationMessage = "ReplInitializationMessage"; + internal const string RequestedVersionRangeNone = "RequestedVersionRangeNone"; + internal const string ScriptArgumentsToolTip = "ScriptArgumentsToolTip"; + internal const string ScriptFileToolTip = "ScriptFileTooltip"; + internal const string Seconds = "Seconds"; + internal const string StartBrowserToolTip = "StartBrowserToolTip"; + internal const string StatusAnalysisLoaded = "StatusAnalysisLoaded"; + internal const string StatusAnalysisLoadFailed = "StatusAnalysisLoadFailed"; + internal const string StatusAnalysisLoading = "StatusAnalysisLoading"; + internal const string StatusAnalysisSaved = "StatusAnalysisSaved"; + internal const string StatusAnalysisSaving = "StatusAnalysisSaving"; + internal const string StatusAnalysisUpToDate = "StatusAnalysisUpToDate"; + internal const string SurroundWith = "SurroundWith"; + internal const string TestFramework = "TestFramework"; + internal const string TestFrameworkDescription = "TestFrameworkDescription"; + internal const string UpgradedEnvironmentVariables = "UpgradedEnvironmentVariables"; + internal const string WorkingDirInvalidOrMissing = "WorkingDirInvalidOrMissing"; + internal const string WorkingDirToolTip = "WorkingDirToolTip"; + + private static readonly Lazy _manager = new Lazy( + () => new System.Resources.ResourceManager("Microsoft.NodejsTools.Resources", typeof(SR).Assembly), + LazyThreadSafetyMode.ExecutionAndPublication + ); + + private static ResourceManager Manager { + get { + return _manager.Value; + } + } + + internal static new string GetString(string value, params object[] args) { + return GetStringInternal(Manager, value, args) ?? CommonSR.GetString(value, args); + } + + internal static string ProductName { + get { + return GetString(NodejsToolsForVisualStudio); + } + } + } +} diff --git a/Nodejs/Product/Nodejs/Repl/NodejsReplEvaluator.cs b/Nodejs/Product/Nodejs/Repl/NodejsReplEvaluator.cs index 2624245f8..f91c2ea68 100644 --- a/Nodejs/Product/Nodejs/Repl/NodejsReplEvaluator.cs +++ b/Nodejs/Product/Nodejs/Repl/NodejsReplEvaluator.cs @@ -228,7 +228,7 @@ private string GetNodeExePath() { var startupProject = _site.GetStartupProject(); string nodeExePath; if (startupProject != null) { - nodeExePath = Nodejs.GetAbsoluteNodeExePath( + nodeExePath = Nodejs.GetAbsoluteNodeExePath( startupProject.ProjectHome, startupProject.GetProjectProperty(NodejsConstants.NodeExePath) ); diff --git a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs index 345089a07..7fb7c2b2d 100644 --- a/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs +++ b/Nodejs/Product/Nodejs/Repl/NpmReplCommand.cs @@ -1,274 +1,274 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.NodejsTools.Npm; -using Microsoft.NodejsTools.Project; -using Microsoft.VisualStudio; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudioTools.Project; -using SR = Microsoft.NodejsTools.Project.SR; -using Task = System.Threading.Tasks.Task; - -namespace Microsoft.NodejsTools.Repl { -#if INTERACTIVE_WINDOW - using IReplCommand = IInteractiveWindowCommand; - using IReplWindow = IInteractiveWindow; -#endif - - [Export(typeof(IReplCommand))] - class NpmReplCommand : IReplCommand { - #region IReplCommand Members - - public async Task Execute(IReplWindow window, string arguments) { - string projectPath = string.Empty; - string npmArguments = arguments.Trim(' ', '\t'); - - // Parse project name/directory in square brackets - if (npmArguments.StartsWith("[")) { - var match = Regex.Match(npmArguments, @"(?:[[]\s*\""?\s*)(.*?)(?:\s*\""?\s*[]]\s*)"); - projectPath = match.Groups[1].Value; - npmArguments = npmArguments.Substring(match.Length); - } - - // Include spaces on either side of npm arguments so that we can more simply detect arguments - // at beginning and end of string (e.g. '--global') - npmArguments = string.Format(" {0} ", npmArguments); - - var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; - IEnumerable loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: true); - - var projectNameToDirectoryDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (IVsProject project in loadedProjects) { - var hierarchy = (IVsHierarchy)project; - object extObject; - - var projectResult = hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out extObject); - if (!ErrorHandler.Succeeded(projectResult)) { - continue; - } - - EnvDTE.Project dteProject = extObject as EnvDTE.Project; - if (dteProject == null) { - continue; - } - - EnvDTE.Properties properties = dteProject.Properties; - if (dteProject.Properties == null) { - continue; - } - - string projectName = dteProject.Name; - EnvDTE.Property projectHome = properties.Item("ProjectHome"); - if (projectHome == null || projectName == null) { - continue; - } - - var projectDirectory = projectHome.Value as string; - if (projectDirectory != null) { - projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy)); - } - } - - Tuple projectInfo; - if (string.IsNullOrEmpty(projectPath) && projectNameToDirectoryDictionary.Count == 1) { - projectInfo = projectNameToDirectoryDictionary.Values.First(); - } else { - projectNameToDirectoryDictionary.TryGetValue(projectPath, out projectInfo); - } - - NodejsProjectNode nodejsProject = null; - if (projectInfo != null) { - projectPath = projectInfo.Item1; - if (projectInfo.Item2 != null) { - nodejsProject = projectInfo.Item2.GetProject().GetNodejsProject(); - } - } - - bool isGlobalCommand = false; - if (string.IsNullOrWhiteSpace(npmArguments) || - npmArguments.Contains(" -g ") || npmArguments.Contains(" --global ")) { - projectPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); - isGlobalCommand = true; - } - - // In case someone copies filename - string projectDirectoryPath = File.Exists(projectPath) ? Path.GetDirectoryName(projectPath) : projectPath; - - if (!isGlobalCommand && !(Directory.Exists(projectDirectoryPath) && File.Exists(Path.Combine(projectDirectoryPath, "package.json")))) { - window.WriteError("Please specify a valid Node.js project or project directory. If your solution contains multiple projects, specify a target project using .npm [ProjectName or ProjectDir] For example: .npm [MyApp] list"); - return ExecutionResult.Failure; - } - - string npmPath; - try { - npmPath = NpmHelpers.GetPathToNpm( - nodejsProject != null ? - Nodejs.GetAbsoluteNodeExePath( - nodejsProject.ProjectHome, - nodejsProject.GetProjectProperty(NodejsConstants.NodeExePath)) - : null); - } catch (NpmNotFoundException) { - Nodejs.ShowNodejsNotInstalled(); - return ExecutionResult.Failure; - } - - var npmReplRedirector = new NpmReplRedirector(window); - - await ExecuteNpmCommandAsync( - npmReplRedirector, - npmPath, - projectDirectoryPath, - new[] { npmArguments }, - null); - - if (npmReplRedirector.HasErrors) { - window.WriteError(SR.GetString(SR.NpmReplCommandCompletedWithErrors, arguments)); - } else { - window.WriteLine(SR.GetString(SR.NpmSuccessfullyCompleted, arguments)); - } - - if (nodejsProject != null) { - await nodejsProject.CheckForLongPaths(npmArguments); - } - - return ExecutionResult.Success; - } - - public string Description { - get { return "Executes npm command. If solution contains multiple projects, specify target project using .npm [ProjectName] "; } - } - - public string Command { - get { return "npm"; } - } - - public object ButtonContent { - get { return null; } - } - - // TODO: This is duplicated from Npm project - // We should consider using InternalsVisibleTo to avoid code duplication - internal static async Task> ExecuteNpmCommandAsync( - Redirector redirector, - string pathToNpm, - string executionDirectory, - string[] arguments, - ManualResetEvent cancellationResetEvent) { - - IEnumerable standardOutputLines = null; - - using (var process = ProcessOutput.Run( - pathToNpm, - arguments, - executionDirectory, - null, - false, - redirector, - quoteArgs: false, - outputEncoding: Encoding.UTF8 // npm uses UTF-8 regardless of locale if its output is redirected - )) { - var whnd = process.WaitHandle; - if (whnd == null) { - // Process failed to start, and any exception message has - // already been sent through the redirector - if (redirector != null) { - redirector.WriteErrorLine("Error - cannot start npm"); - } - } else { - var handles = cancellationResetEvent != null ? new[] { whnd, cancellationResetEvent } : new [] { whnd }; - var i = await Task.Run(() => WaitHandle.WaitAny(handles)); - if (i == 0) { - Debug.Assert(process.ExitCode.HasValue, "npm process has not really exited"); - process.Wait(); - if (process.StandardOutputLines != null) { - standardOutputLines = process.StandardOutputLines.ToList(); - } - } else { - process.Kill(); - if (redirector != null) { - redirector.WriteErrorLine(string.Format( - "\r\n===={0}====\r\n\r\n", - "npm command cancelled")); - } - - if (cancellationResetEvent != null) { - cancellationResetEvent.Reset(); - } - - throw new OperationCanceledException(); - } - } - } - return standardOutputLines; - } - - #endregion - - internal class NpmReplRedirector : Redirector { - - internal const string ErrorAnsiColor = "\x1b[31;1m"; - internal const string WarnAnsiColor = "\x1b[33;22m"; - internal const string NormalAnsiColor = "\x1b[39;49m"; - - private const string ErrorText = "npm ERR!"; - private const string WarningText = "npm WARN"; - - private IReplWindow _window; - - public NpmReplRedirector(IReplWindow window) { - _window = window; - HasErrors = false; - } - public bool HasErrors { get; set; } - - public override void WriteLine(string decodedString) { - var substring = string.Empty; - string outputString = string.Empty; - - if (decodedString.StartsWith(ErrorText)) { - outputString += ErrorAnsiColor + decodedString.Substring(0, ErrorText.Length); - substring = decodedString.Length > ErrorText.Length ? decodedString.Substring(ErrorText.Length) : string.Empty; - this.HasErrors = true; - } else if (decodedString.StartsWith(WarningText)) { - outputString += WarnAnsiColor + decodedString.Substring(0, WarningText.Length); - substring = decodedString.Length > WarningText.Length ? decodedString.Substring(WarningText.Length) : string.Empty; - } else { - substring = decodedString; - } - - outputString += NormalAnsiColor + substring; - - _window.WriteLine(outputString); - Debug.WriteLine(decodedString, "REPL npm"); - } - - public override void WriteErrorLine(string line) { - _window.WriteError(line); - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.NodejsTools.Npm; +using Microsoft.NodejsTools.Project; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudioTools.Project; +using SR = Microsoft.NodejsTools.Project.SR; +using Task = System.Threading.Tasks.Task; + +namespace Microsoft.NodejsTools.Repl { +#if INTERACTIVE_WINDOW + using IReplCommand = IInteractiveWindowCommand; + using IReplWindow = IInteractiveWindow; +#endif + + [Export(typeof(IReplCommand))] + class NpmReplCommand : IReplCommand { + #region IReplCommand Members + + public async Task Execute(IReplWindow window, string arguments) { + string projectPath = string.Empty; + string npmArguments = arguments.Trim(' ', '\t'); + + // Parse project name/directory in square brackets + if (npmArguments.StartsWith("[")) { + var match = Regex.Match(npmArguments, @"(?:[[]\s*\""?\s*)(.*?)(?:\s*\""?\s*[]]\s*)"); + projectPath = match.Groups[1].Value; + npmArguments = npmArguments.Substring(match.Length); + } + + // Include spaces on either side of npm arguments so that we can more simply detect arguments + // at beginning and end of string (e.g. '--global') + npmArguments = string.Format(" {0} ", npmArguments); + + var solution = Package.GetGlobalService(typeof(SVsSolution)) as IVsSolution; + IEnumerable loadedProjects = solution.EnumerateLoadedProjects(onlyNodeProjects: true); + + var projectNameToDirectoryDictionary = new Dictionary>(StringComparer.OrdinalIgnoreCase); + foreach (IVsProject project in loadedProjects) { + var hierarchy = (IVsHierarchy)project; + object extObject; + + var projectResult = hierarchy.GetProperty(VSConstants.VSITEMID_ROOT, (int)__VSHPROPID.VSHPROPID_ExtObject, out extObject); + if (!ErrorHandler.Succeeded(projectResult)) { + continue; + } + + EnvDTE.Project dteProject = extObject as EnvDTE.Project; + if (dteProject == null) { + continue; + } + + EnvDTE.Properties properties = dteProject.Properties; + if (dteProject.Properties == null) { + continue; + } + + string projectName = dteProject.Name; + EnvDTE.Property projectHome = properties.Item("ProjectHome"); + if (projectHome == null || projectName == null) { + continue; + } + + var projectDirectory = projectHome.Value as string; + if (projectDirectory != null) { + projectNameToDirectoryDictionary.Add(projectName, Tuple.Create(projectDirectory, hierarchy)); + } + } + + Tuple projectInfo; + if (string.IsNullOrEmpty(projectPath) && projectNameToDirectoryDictionary.Count == 1) { + projectInfo = projectNameToDirectoryDictionary.Values.First(); + } else { + projectNameToDirectoryDictionary.TryGetValue(projectPath, out projectInfo); + } + + NodejsProjectNode nodejsProject = null; + if (projectInfo != null) { + projectPath = projectInfo.Item1; + if (projectInfo.Item2 != null) { + nodejsProject = projectInfo.Item2.GetProject().GetNodejsProject(); + } + } + + bool isGlobalCommand = false; + if (string.IsNullOrWhiteSpace(npmArguments) || + npmArguments.Contains(" -g ") || npmArguments.Contains(" --global ")) { + projectPath = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); + isGlobalCommand = true; + } + + // In case someone copies filename + string projectDirectoryPath = File.Exists(projectPath) ? Path.GetDirectoryName(projectPath) : projectPath; + + if (!isGlobalCommand && !(Directory.Exists(projectDirectoryPath) && File.Exists(Path.Combine(projectDirectoryPath, "package.json")))) { + window.WriteError("Please specify a valid Node.js project or project directory. If your solution contains multiple projects, specify a target project using .npm [ProjectName or ProjectDir] For example: .npm [MyApp] list"); + return ExecutionResult.Failure; + } + + string npmPath; + try { + npmPath = NpmHelpers.GetPathToNpm( + nodejsProject != null ? + Nodejs.GetAbsoluteNodeExePath( + nodejsProject.ProjectHome, + nodejsProject.GetProjectProperty(NodejsConstants.NodeExePath)) + : null); + } catch (NpmNotFoundException) { + Nodejs.ShowNodejsNotInstalled(); + return ExecutionResult.Failure; + } + + var npmReplRedirector = new NpmReplRedirector(window); + + await ExecuteNpmCommandAsync( + npmReplRedirector, + npmPath, + projectDirectoryPath, + new[] { npmArguments }, + null); + + if (npmReplRedirector.HasErrors) { + window.WriteError(SR.GetString(SR.NpmReplCommandCompletedWithErrors, arguments)); + } else { + window.WriteLine(SR.GetString(SR.NpmSuccessfullyCompleted, arguments)); + } + + if (nodejsProject != null) { + await nodejsProject.CheckForLongPaths(npmArguments); + } + + return ExecutionResult.Success; + } + + public string Description { + get { return "Executes npm command. If solution contains multiple projects, specify target project using .npm [ProjectName] "; } + } + + public string Command { + get { return "npm"; } + } + + public object ButtonContent { + get { return null; } + } + + // TODO: This is duplicated from Npm project + // We should consider using InternalsVisibleTo to avoid code duplication + internal static async Task> ExecuteNpmCommandAsync( + Redirector redirector, + string pathToNpm, + string executionDirectory, + string[] arguments, + ManualResetEvent cancellationResetEvent) { + + IEnumerable standardOutputLines = null; + + using (var process = ProcessOutput.Run( + pathToNpm, + arguments, + executionDirectory, + null, + false, + redirector, + quoteArgs: false, + outputEncoding: Encoding.UTF8 // npm uses UTF-8 regardless of locale if its output is redirected + )) { + var whnd = process.WaitHandle; + if (whnd == null) { + // Process failed to start, and any exception message has + // already been sent through the redirector + if (redirector != null) { + redirector.WriteErrorLine("Error - cannot start npm"); + } + } else { + var handles = cancellationResetEvent != null ? new[] { whnd, cancellationResetEvent } : new [] { whnd }; + var i = await Task.Run(() => WaitHandle.WaitAny(handles)); + if (i == 0) { + Debug.Assert(process.ExitCode.HasValue, "npm process has not really exited"); + process.Wait(); + if (process.StandardOutputLines != null) { + standardOutputLines = process.StandardOutputLines.ToList(); + } + } else { + process.Kill(); + if (redirector != null) { + redirector.WriteErrorLine(string.Format( + "\r\n===={0}====\r\n\r\n", + "npm command cancelled")); + } + + if (cancellationResetEvent != null) { + cancellationResetEvent.Reset(); + } + + throw new OperationCanceledException(); + } + } + } + return standardOutputLines; + } + + #endregion + + internal class NpmReplRedirector : Redirector { + + internal const string ErrorAnsiColor = "\x1b[31;1m"; + internal const string WarnAnsiColor = "\x1b[33;22m"; + internal const string NormalAnsiColor = "\x1b[39;49m"; + + private const string ErrorText = "npm ERR!"; + private const string WarningText = "npm WARN"; + + private IReplWindow _window; + + public NpmReplRedirector(IReplWindow window) { + _window = window; + HasErrors = false; + } + public bool HasErrors { get; set; } + + public override void WriteLine(string decodedString) { + var substring = string.Empty; + string outputString = string.Empty; + + if (decodedString.StartsWith(ErrorText)) { + outputString += ErrorAnsiColor + decodedString.Substring(0, ErrorText.Length); + substring = decodedString.Length > ErrorText.Length ? decodedString.Substring(ErrorText.Length) : string.Empty; + this.HasErrors = true; + } else if (decodedString.StartsWith(WarningText)) { + outputString += WarnAnsiColor + decodedString.Substring(0, WarningText.Length); + substring = decodedString.Length > WarningText.Length ? decodedString.Substring(WarningText.Length) : string.Empty; + } else { + substring = decodedString; + } + + outputString += NormalAnsiColor + substring; + + _window.WriteLine(outputString); + Debug.WriteLine(decodedString, "REPL npm"); + } + + public override void WriteErrorLine(string line) { + _window.WriteError(line); + } + } + } +} diff --git a/Nodejs/Product/Nodejs/SourceMapping/SourceMapper.cs b/Nodejs/Product/Nodejs/SourceMapping/SourceMapper.cs index 2661c91a4..2f34cde0c 100644 --- a/Nodejs/Product/Nodejs/SourceMapping/SourceMapper.cs +++ b/Nodejs/Product/Nodejs/SourceMapping/SourceMapper.cs @@ -21,8 +21,8 @@ using System.Linq; using Microsoft.NodejsTools; -using Microsoft.VisualStudioTools; - +using Microsoft.VisualStudioTools; + namespace Microsoft.NodejsTools.SourceMapping { internal class SourceMapper { private readonly Dictionary _generatedFileToSourceMap = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -151,26 +151,26 @@ internal bool MapToJavaScript(string requestedFileName, int requestedLineNo, int } return false; - } - - /// - /// Get the original file name to map to. - /// - /// JavaScript compiled file. + } + + /// + /// Get the original file name to map to. + /// + /// JavaScript compiled file. /// Line number /// Column number - internal string GetOriginalFileName(string javaScriptFileName, int? line, int? column) { - string originalFileName = null; - + internal string GetOriginalFileName(string javaScriptFileName, int? line, int? column) { + string originalFileName = null; + if (line != null && column != null) { SourceMapInfo tempMapping = this.MapToOriginal(javaScriptFileName, (int)line, (int)column); if (tempMapping != null) { originalFileName = tempMapping.FileName; } - } - - return originalFileName ?? this.MapToOriginal(javaScriptFileName); + } + + return originalFileName ?? this.MapToOriginal(javaScriptFileName); } private static string GetFileRelativeToFile(string relativeToFile, string newFileName) { diff --git a/Nodejs/Product/Npm/DependencyType.cs b/Nodejs/Product/Npm/DependencyType.cs index 218baf58e..3eb8144a5 100644 --- a/Nodejs/Product/Npm/DependencyType.cs +++ b/Nodejs/Product/Npm/DependencyType.cs @@ -1,23 +1,23 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public enum DependencyType { - Standard = 0, - Development, - Optional - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public enum DependencyType { + Standard = 0, + Development, + Optional + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/DependencyUrlType.cs b/Nodejs/Product/Npm/DependencyUrlType.cs index 6d5333b9a..1cf1aeb34 100644 --- a/Nodejs/Product/Npm/DependencyUrlType.cs +++ b/Nodejs/Product/Npm/DependencyUrlType.cs @@ -1,27 +1,27 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public enum DependencyUrlType { - UnsupportedProtocol, - Http, - Git, - GitSsh, - GitHttp, - GitHttps, - GitHub - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public enum DependencyUrlType { + UnsupportedProtocol, + Http, + Git, + GitSsh, + GitHttp, + GitHttps, + GitHub + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/DirectoryPackageJsonSource.cs b/Nodejs/Product/Npm/DirectoryPackageJsonSource.cs index 478dff4a2..c6ed94eda 100644 --- a/Nodejs/Product/Npm/DirectoryPackageJsonSource.cs +++ b/Nodejs/Product/Npm/DirectoryPackageJsonSource.cs @@ -1,31 +1,31 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.IO; - -namespace Microsoft.NodejsTools.Npm { - public class DirectoryPackageJsonSource : IPackageJsonSource { - private readonly FilePackageJsonSource _source; - - public DirectoryPackageJsonSource(string fullDirectoryPath) { - _source = new FilePackageJsonSource(Path.Combine(fullDirectoryPath, "package.json")); - } - - public dynamic Package { - get { return _source.Package; } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.IO; + +namespace Microsoft.NodejsTools.Npm { + public class DirectoryPackageJsonSource : IPackageJsonSource { + private readonly FilePackageJsonSource _source; + + public DirectoryPackageJsonSource(string fullDirectoryPath) { + _source = new FilePackageJsonSource(Path.Combine(fullDirectoryPath, "package.json")); + } + + public dynamic Package { + get { return _source.Package; } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/FilePackageJsonSource.cs b/Nodejs/Product/Npm/FilePackageJsonSource.cs index 78138cafa..194bdde03 100644 --- a/Nodejs/Product/Npm/FilePackageJsonSource.cs +++ b/Nodejs/Product/Npm/FilePackageJsonSource.cs @@ -1,63 +1,63 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.IO; -using System.Threading; - -namespace Microsoft.NodejsTools.Npm { - public class FilePackageJsonSource : IPackageJsonSource { - - private readonly ReaderPackageJsonSource _source; - - public FilePackageJsonSource(string fullPathToFile) { - if (File.Exists(fullPathToFile)) { - int retryInterval = 500; - int attempts = 5; - - // populate _source with retries for recoverable errors. - while (--attempts >= 0) { - try { - using (var fin = new FileStream(fullPathToFile, FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var reader = new StreamReader(fin)) { - _source = new ReaderPackageJsonSource(reader); - break; - } - } catch (PackageJsonException pje) { - WrapExceptionAndRethrow(fullPathToFile, pje); - } catch (IOException) { - if (attempts <= 0) { throw; } - } catch (UnauthorizedAccessException) { - if (attempts <= 0) { throw; } - } - - Thread.Sleep(retryInterval); - retryInterval *= 2; // exponential backoff - } - } - } - - private void WrapExceptionAndRethrow( - string fullPathToFile, - Exception ex) { - throw new PackageJsonException( - string.Format(@"Error reading package.json at '{0}': {1}", fullPathToFile, ex.Message), - ex); - } - - public dynamic Package { get { return null == _source ? null : _source.Package; } } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.IO; +using System.Threading; + +namespace Microsoft.NodejsTools.Npm { + public class FilePackageJsonSource : IPackageJsonSource { + + private readonly ReaderPackageJsonSource _source; + + public FilePackageJsonSource(string fullPathToFile) { + if (File.Exists(fullPathToFile)) { + int retryInterval = 500; + int attempts = 5; + + // populate _source with retries for recoverable errors. + while (--attempts >= 0) { + try { + using (var fin = new FileStream(fullPathToFile, FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var reader = new StreamReader(fin)) { + _source = new ReaderPackageJsonSource(reader); + break; + } + } catch (PackageJsonException pje) { + WrapExceptionAndRethrow(fullPathToFile, pje); + } catch (IOException) { + if (attempts <= 0) { throw; } + } catch (UnauthorizedAccessException) { + if (attempts <= 0) { throw; } + } + + Thread.Sleep(retryInterval); + retryInterval *= 2; // exponential backoff + } + } + } + + private void WrapExceptionAndRethrow( + string fullPathToFile, + Exception ex) { + throw new PackageJsonException( + string.Format(@"Error reading package.json at '{0}': {1}", fullPathToFile, ex.Message), + ex); + } + + public dynamic Package { get { return null == _source ? null : _source.Package; } } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IBugs.cs b/Nodejs/Product/Npm/IBugs.cs index e45d1c899..be80b3586 100644 --- a/Nodejs/Product/Npm/IBugs.cs +++ b/Nodejs/Product/Npm/IBugs.cs @@ -1,22 +1,22 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IBugs { - string Url { get; } - string Email { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IBugs { + string Url { get; } + string Email { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IBundledDependencies.cs b/Nodejs/Product/Npm/IBundledDependencies.cs index b380dbb44..287063455 100644 --- a/Nodejs/Product/Npm/IBundledDependencies.cs +++ b/Nodejs/Product/Npm/IBundledDependencies.cs @@ -1,19 +1,19 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IBundledDependencies : IPkgStringArray { } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IBundledDependencies : IPkgStringArray { } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IDependencies.cs b/Nodejs/Product/Npm/IDependencies.cs index 4200d0d4c..d6533413b 100644 --- a/Nodejs/Product/Npm/IDependencies.cs +++ b/Nodejs/Product/Npm/IDependencies.cs @@ -1,25 +1,25 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm { - public interface IDependencies : IEnumerable { - int Count { get; } - IDependency this[string name] { get; } - bool Contains(string name); - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm { + public interface IDependencies : IEnumerable { + int Count { get; } + IDependency this[string name] { get; } + bool Contains(string name); + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IDependency.cs b/Nodejs/Product/Npm/IDependency.cs index 809f56d72..e48d71726 100644 --- a/Nodejs/Product/Npm/IDependency.cs +++ b/Nodejs/Product/Npm/IDependency.cs @@ -1,23 +1,23 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IDependency { - string Name { get; } - IDependencyUrl Url { get; } - string VersionRangeText { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IDependency { + string Name { get; } + IDependencyUrl Url { get; } + string VersionRangeText { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IDependencyUrl.cs b/Nodejs/Product/Npm/IDependencyUrl.cs index d6f6250f1..6b0c94b52 100644 --- a/Nodejs/Product/Npm/IDependencyUrl.cs +++ b/Nodejs/Product/Npm/IDependencyUrl.cs @@ -1,22 +1,22 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IDependencyUrl { - string Address { get; } - DependencyUrlType Type { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IDependencyUrl { + string Address { get; } + DependencyUrlType Type { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IFiles.cs b/Nodejs/Product/Npm/IFiles.cs index 7c47d2ff4..556c7816a 100644 --- a/Nodejs/Product/Npm/IFiles.cs +++ b/Nodejs/Product/Npm/IFiles.cs @@ -1,19 +1,19 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IFiles : IPkgStringArray { } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IFiles : IPkgStringArray { } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IGlobalPackages.cs b/Nodejs/Product/Npm/IGlobalPackages.cs index aa915d233..4f79306c1 100644 --- a/Nodejs/Product/Npm/IGlobalPackages.cs +++ b/Nodejs/Product/Npm/IGlobalPackages.cs @@ -1,19 +1,19 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IGlobalPackages : IRootPackage { } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IGlobalPackages : IRootPackage { } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IKeywords.cs b/Nodejs/Product/Npm/IKeywords.cs index 9b0638a63..98af38e3d 100644 --- a/Nodejs/Product/Npm/IKeywords.cs +++ b/Nodejs/Product/Npm/IKeywords.cs @@ -1,19 +1,19 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IKeywords : IPkgStringArray { } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IKeywords : IPkgStringArray { } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/ILicense.cs b/Nodejs/Product/Npm/ILicense.cs index b5e8e29ec..6e656929a 100644 --- a/Nodejs/Product/Npm/ILicense.cs +++ b/Nodejs/Product/Npm/ILicense.cs @@ -1,22 +1,22 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface ILicense { - string Type { get; } - string Url { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface ILicense { + string Type { get; } + string Url { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/ILicenses.cs b/Nodejs/Product/Npm/ILicenses.cs index 33dbab380..64dd14a0e 100644 --- a/Nodejs/Product/Npm/ILicenses.cs +++ b/Nodejs/Product/Npm/ILicenses.cs @@ -1,22 +1,22 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface ILicenses { - int Count { get; } - ILicense this[int index] { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface ILicenses { + int Count { get; } + ILicense this[int index] { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IMan.cs b/Nodejs/Product/Npm/IMan.cs index 8dbbefef7..d01986280 100644 --- a/Nodejs/Product/Npm/IMan.cs +++ b/Nodejs/Product/Npm/IMan.cs @@ -1,19 +1,19 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IMan : IPkgStringArray { } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IMan : IPkgStringArray { } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/INodeModules.cs b/Nodejs/Product/Npm/INodeModules.cs index 2e6dfccb7..b934ce2de 100644 --- a/Nodejs/Product/Npm/INodeModules.cs +++ b/Nodejs/Product/Npm/INodeModules.cs @@ -1,28 +1,28 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm { - public interface INodeModules : IEnumerable { - int Count { get; } - IPackage this[int index] { get; } - IPackage this[string name] { get; } - bool Contains(string name); - bool HasMissingModules { get; } - int GetDepth(string filepath); - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm { + public interface INodeModules : IEnumerable { + int Count { get; } + IPackage this[int index] { get; } + IPackage this[string name] { get; } + bool Contains(string name); + bool HasMissingModules { get; } + int GetDepth(string filepath); + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/INpmCommander.cs b/Nodejs/Product/Npm/INpmCommander.cs index 443548521..7af5919cf 100644 --- a/Nodejs/Product/Npm/INpmCommander.cs +++ b/Nodejs/Product/Npm/INpmCommander.cs @@ -1,100 +1,100 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - public interface INpmCommander : INpmLogSource, IDisposable { - - /// - /// Cancels the currently running command - /// - void CancelCurrentCommand(); - - /// - /// Executes npm install to install all packages in package.json. - /// - /// - Task Install(); - - /// - /// - /// - /// - /// - /// - /// - /// If there is an error reading a package.json file when modules are refreshed. - /// - Task InstallPackageByVersionAsync( - string packageName, - string versionRange, - DependencyType type, - bool saveToPackageJson); - - /// - /// - /// - /// - /// - /// If there is an error reading a package.json file when modules are refreshed. - /// - Task InstallGlobalPackageByVersionAsync( - string packageName, - string versionRange); - - /// - /// - /// - /// - /// If there is an error reading a package.json file when modules are refreshed. - /// - Task UninstallPackageAsync(string packageName); - - Task UninstallGlobalPackageAsync(string packageName); - - Task GetCatalogAsync(bool forceDownload, IProgress progress); - - Task UpdatePackagesAsync(); - - /// - /// - /// - /// - /// If there is an error reading a package.json file when modules are refreshed. - /// - Task UpdatePackagesAsync(IEnumerable packages); - - /// - /// - /// - /// - /// If there is an error reading a package.json file when modules are refreshed. - /// - Task UpdateGlobalPackagesAsync(IEnumerable packages); - - /// - /// - /// - /// - /// - Task ExecuteNpmCommandAsync(string arguments); - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + public interface INpmCommander : INpmLogSource, IDisposable { + + /// + /// Cancels the currently running command + /// + void CancelCurrentCommand(); + + /// + /// Executes npm install to install all packages in package.json. + /// + /// + Task Install(); + + /// + /// + /// + /// + /// + /// + /// + /// If there is an error reading a package.json file when modules are refreshed. + /// + Task InstallPackageByVersionAsync( + string packageName, + string versionRange, + DependencyType type, + bool saveToPackageJson); + + /// + /// + /// + /// + /// + /// If there is an error reading a package.json file when modules are refreshed. + /// + Task InstallGlobalPackageByVersionAsync( + string packageName, + string versionRange); + + /// + /// + /// + /// + /// If there is an error reading a package.json file when modules are refreshed. + /// + Task UninstallPackageAsync(string packageName); + + Task UninstallGlobalPackageAsync(string packageName); + + Task GetCatalogAsync(bool forceDownload, IProgress progress); + + Task UpdatePackagesAsync(); + + /// + /// + /// + /// + /// If there is an error reading a package.json file when modules are refreshed. + /// + Task UpdatePackagesAsync(IEnumerable packages); + + /// + /// + /// + /// + /// If there is an error reading a package.json file when modules are refreshed. + /// + Task UpdateGlobalPackagesAsync(IEnumerable packages); + + /// + /// + /// + /// + /// + Task ExecuteNpmCommandAsync(string arguments); + } +} diff --git a/Nodejs/Product/Npm/INpmController.cs b/Nodejs/Product/Npm/INpmController.cs index 746f4b020..11e2c98da 100644 --- a/Nodejs/Product/Npm/INpmController.cs +++ b/Nodejs/Product/Npm/INpmController.cs @@ -1,41 +1,41 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - public interface INpmController : INpmLogSource, IDisposable { - event EventHandler StartingRefresh; - - void Refresh(); - - event EventHandler FinishedRefresh; - - IRootPackage RootPackage { get; } - - IGlobalPackages GlobalPackages { get; } - - INpmCommander CreateNpmCommander(); - - Task GetRepositoryCatalogAsync(bool forceDownload, IProgress progress); - - IPackageCatalog MostRecentlyLoadedCatalog { get; } - - string ListBaseDirectory { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + public interface INpmController : INpmLogSource, IDisposable { + event EventHandler StartingRefresh; + + void Refresh(); + + event EventHandler FinishedRefresh; + + IRootPackage RootPackage { get; } + + IGlobalPackages GlobalPackages { get; } + + INpmCommander CreateNpmCommander(); + + Task GetRepositoryCatalogAsync(bool forceDownload, IProgress progress); + + IPackageCatalog MostRecentlyLoadedCatalog { get; } + + string ListBaseDirectory { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/INpmLogSource.cs b/Nodejs/Product/Npm/INpmLogSource.cs index 59d17b11e..039226490 100644 --- a/Nodejs/Product/Npm/INpmLogSource.cs +++ b/Nodejs/Product/Npm/INpmLogSource.cs @@ -1,27 +1,27 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; - -namespace Microsoft.NodejsTools.Npm { - public interface INpmLogSource { - event EventHandler CommandStarted; - event EventHandler OutputLogged; - event EventHandler ErrorLogged; - event EventHandler ExceptionLogged; - event EventHandler CommandCompleted; - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; + +namespace Microsoft.NodejsTools.Npm { + public interface INpmLogSource { + event EventHandler CommandStarted; + event EventHandler OutputLogged; + event EventHandler ErrorLogged; + event EventHandler ExceptionLogged; + event EventHandler CommandCompleted; + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/INpmPathProvider.cs b/Nodejs/Product/Npm/INpmPathProvider.cs index 25dd58791..16a34818b 100644 --- a/Nodejs/Product/Npm/INpmPathProvider.cs +++ b/Nodejs/Product/Npm/INpmPathProvider.cs @@ -1,27 +1,27 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - public interface INpmPathProvider { - string PathToNpm { get; } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + public interface INpmPathProvider { + string PathToNpm { get; } + } +} diff --git a/Nodejs/Product/Npm/IPackage.cs b/Nodejs/Product/Npm/IPackage.cs index 1f251c97c..723af9ddf 100644 --- a/Nodejs/Product/Npm/IPackage.cs +++ b/Nodejs/Product/Npm/IPackage.cs @@ -1,44 +1,44 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections; -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm { - public interface IPackage : IRootPackage { - string PublishDateTimeString { get; } - - string RequestedVersionRange { get; } - - IEnumerable Keywords { get; } - - IEnumerable AvailableVersions { get; } - - bool IsListedInParentPackageJson { get; } - - bool IsMissing { get; } - - bool IsDependency { get; } - - bool IsDevDependency { get; } - - bool IsOptionalDependency { get; } - - bool IsBundledDependency { get; } - - PackageFlags Flags { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm { + public interface IPackage : IRootPackage { + string PublishDateTimeString { get; } + + string RequestedVersionRange { get; } + + IEnumerable Keywords { get; } + + IEnumerable AvailableVersions { get; } + + bool IsListedInParentPackageJson { get; } + + bool IsMissing { get; } + + bool IsDependency { get; } + + bool IsDevDependency { get; } + + bool IsOptionalDependency { get; } + + bool IsBundledDependency { get; } + + PackageFlags Flags { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IPackageCatalog.cs b/Nodejs/Product/Npm/IPackageCatalog.cs index 0da8c12b0..a815b4dd4 100644 --- a/Nodejs/Product/Npm/IPackageCatalog.cs +++ b/Nodejs/Product/Npm/IPackageCatalog.cs @@ -1,33 +1,33 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - public interface IPackageCatalog { - DateTime LastRefreshed { get; } - - Task> GetCatalogPackagesAsync(string filterText, Uri registryUrl = null); - - IPackage this[string name] { get; } - - long? ResultsCount { get; } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + public interface IPackageCatalog { + DateTime LastRefreshed { get; } + + Task> GetCatalogPackagesAsync(string filterText, Uri registryUrl = null); + + IPackage this[string name] { get; } + + long? ResultsCount { get; } + } +} diff --git a/Nodejs/Product/Npm/IPackageCatalogFilter.cs b/Nodejs/Product/Npm/IPackageCatalogFilter.cs index bc1b9188f..a437cf61d 100644 --- a/Nodejs/Product/Npm/IPackageCatalogFilter.cs +++ b/Nodejs/Product/Npm/IPackageCatalogFilter.cs @@ -1,47 +1,47 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - - /// - /// Used to filter the entire package catalog down to a more manageable/relevant - /// list. - /// - public interface IPackageCatalogFilter { - - /// - /// Filters the entire package list based on the supplied filter string and returns - /// any packages that match. If filterString starts with a '/' it will be treated as - /// a regular expression. In this case, if that last character in the string is also a - /// '/' it will be strimmed and ignored, consistent with npm's command line behaviour. - /// - /// - /// String or regular expression, denoted by a leading '/' character, with which to - /// filter the package catalog. If the string is null or empty all packages will be returned. - /// - /// - /// List of matching packages. If there are no matches an empty list is returned. - /// - IEnumerable Filter(string filterString); - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + + /// + /// Used to filter the entire package catalog down to a more manageable/relevant + /// list. + /// + public interface IPackageCatalogFilter { + + /// + /// Filters the entire package list based on the supplied filter string and returns + /// any packages that match. If filterString starts with a '/' it will be treated as + /// a regular expression. In this case, if that last character in the string is also a + /// '/' it will be strimmed and ignored, consistent with npm's command line behaviour. + /// + /// + /// String or regular expression, denoted by a leading '/' character, with which to + /// filter the package catalog. If the string is null or empty all packages will be returned. + /// + /// + /// List of matching packages. If there are no matches an empty list is returned. + /// + IEnumerable Filter(string filterString); + } +} diff --git a/Nodejs/Product/Npm/IPackageJson.cs b/Nodejs/Product/Npm/IPackageJson.cs index ac3f66e00..1adddeabb 100644 --- a/Nodejs/Product/Npm/IPackageJson.cs +++ b/Nodejs/Product/Npm/IPackageJson.cs @@ -1,39 +1,39 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm { - public interface IPackageJson { - string Name { get; } - SemverVersion Version { get; } - IScripts Scripts { get; } - IPerson Author { get; } - string Description { get; } - IKeywords Keywords { get; } - IHomepages Homepages { get; } - IBugs Bugs { get; } - ILicenses Licenses { get; } - IFiles Files { get; } - IMan Man { get; } - IDependencies Dependencies { get; } - IDependencies DevDependencies { get; } - IBundledDependencies BundledDependencies { get; } - IDependencies OptionalDependencies { get; } - IDependencies AllDependencies { get; } - IEnumerable RequiredBy { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm { + public interface IPackageJson { + string Name { get; } + SemverVersion Version { get; } + IScripts Scripts { get; } + IPerson Author { get; } + string Description { get; } + IKeywords Keywords { get; } + IHomepages Homepages { get; } + IBugs Bugs { get; } + ILicenses Licenses { get; } + IFiles Files { get; } + IMan Man { get; } + IDependencies Dependencies { get; } + IDependencies DevDependencies { get; } + IBundledDependencies BundledDependencies { get; } + IDependencies OptionalDependencies { get; } + IDependencies AllDependencies { get; } + IEnumerable RequiredBy { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IPackageJsonSource.cs b/Nodejs/Product/Npm/IPackageJsonSource.cs index 571e7b90a..2973e9e70 100644 --- a/Nodejs/Product/Npm/IPackageJsonSource.cs +++ b/Nodejs/Product/Npm/IPackageJsonSource.cs @@ -1,21 +1,21 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IPackageJsonSource { - dynamic Package { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IPackageJsonSource { + dynamic Package { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IPerson.cs b/Nodejs/Product/Npm/IPerson.cs index f22003321..9630b5610 100644 --- a/Nodejs/Product/Npm/IPerson.cs +++ b/Nodejs/Product/Npm/IPerson.cs @@ -1,23 +1,23 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IPerson { - string Name { get; } - string Email { get; } - string Url { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IPerson { + string Name { get; } + string Email { get; } + string Url { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IPkgStringArray.cs b/Nodejs/Product/Npm/IPkgStringArray.cs index 60c302054..9851375f6 100644 --- a/Nodejs/Product/Npm/IPkgStringArray.cs +++ b/Nodejs/Product/Npm/IPkgStringArray.cs @@ -1,24 +1,24 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm { - public interface IPkgStringArray : IEnumerable { - int Count { get; } - string this[int index] { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm { + public interface IPkgStringArray : IEnumerable { + int Count { get; } + string this[int index] { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IRootPackage.cs b/Nodejs/Product/Npm/IRootPackage.cs index 243b57375..91ce5fd06 100644 --- a/Nodejs/Product/Npm/IRootPackage.cs +++ b/Nodejs/Product/Npm/IRootPackage.cs @@ -1,39 +1,39 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm { - public interface IRootPackage { - INodeModules Modules { get; } - - IPackageJson PackageJson { get; } - - bool HasPackageJson { get; } - - string Name { get; } - - SemverVersion Version { get; } - - IPerson Author { get; } - - string Description { get; } - - IEnumerable Homepages { get; } - - string Path { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm { + public interface IRootPackage { + INodeModules Modules { get; } + + IPackageJson PackageJson { get; } + + bool HasPackageJson { get; } + + string Name { get; } + + SemverVersion Version { get; } + + IPerson Author { get; } + + string Description { get; } + + IEnumerable Homepages { get; } + + string Path { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IScript.cs b/Nodejs/Product/Npm/IScript.cs index b69ba21d1..4a7ca8814 100644 --- a/Nodejs/Product/Npm/IScript.cs +++ b/Nodejs/Product/Npm/IScript.cs @@ -1,22 +1,22 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IScript { - string Name { get; } - string Code { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IScript { + string Name { get; } + string Code { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/IScripts.cs b/Nodejs/Product/Npm/IScripts.cs index 6b77479dc..6b24bf73e 100644 --- a/Nodejs/Product/Npm/IScripts.cs +++ b/Nodejs/Product/Npm/IScripts.cs @@ -1,22 +1,22 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public interface IScripts { - int Count { get; } - IScript this[string name] { get; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public interface IScripts { + int Count { get; } + IScript this[string name] { get; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/NativeMethods.cs b/Nodejs/Product/Npm/NativeMethods.cs index cbf0bba95..d847d985f 100644 --- a/Nodejs/Product/Npm/NativeMethods.cs +++ b/Nodejs/Product/Npm/NativeMethods.cs @@ -1,22 +1,22 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - internal static class NativeMethods { - public const int MAX_PATH = 260; // windef.h - public const int MAX_FOLDER_PATH = MAX_PATH - 12; // folders need to allow 8.3 filenames, so MAX_PATH - 12 - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + internal static class NativeMethods { + public const int MAX_PATH = 260; // windef.h + public const int MAX_FOLDER_PATH = MAX_PATH - 12; // folders need to allow 8.3 filenames, so MAX_PATH - 12 + } +} diff --git a/Nodejs/Product/Npm/NodeModuleBuilder.cs b/Nodejs/Product/Npm/NodeModuleBuilder.cs index 61dd74e2d..c1c3e5de0 100644 --- a/Nodejs/Product/Npm/NodeModuleBuilder.cs +++ b/Nodejs/Product/Npm/NodeModuleBuilder.cs @@ -1,170 +1,170 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Emit; -using System.Text; -using Microsoft.NodejsTools.Npm.SPI; - -namespace Microsoft.NodejsTools.Npm { - /// - /// Mutable class for building immutable node module descriptions - /// - internal class NodeModuleBuilder { - private List _dependencies = new List(); - private readonly StringBuilder _descriptionBuff = new StringBuilder(); - private readonly StringBuilder _authorBuff = new StringBuilder(); - private readonly StringBuilder _publishDateTime = new StringBuilder(); - private List _keywords = new List(); - private List _homepages = new List(); - private List _availableVersions = new List(); - - public NodeModuleBuilder() { - Reset(); - } - - public void Reset() { - Name = null; - - // We should double check, but I believe that the package no longer exists when "latest" is not set. - // If that's the case, we should include an option to filter out those packages. - // https://nodejstools.codeplex.com/workitem/1452 - LatestVersion = SemverVersion.UnknownVersion; - _availableVersions = new List(); - - Flags = PackageFlags.None; - RequestedVersionRange = null; - - // These *have* to be reinitialised or they'll be cleared - // in any packages that have been created using the builder - // because they're passed by reference. - _dependencies = new List(); - _keywords = new List(); - _homepages = new List(); - - _descriptionBuff.Length = 0; - _authorBuff.Length = 0; - _publishDateTime.Length = 0; - } - - public void AddAuthor(string text) { - if (_authorBuff.Length > 0) { - _authorBuff.Append(' '); - } - _authorBuff.Append(text); - } - - public IPerson Author { - get { - var text = _authorBuff.ToString().Trim(); - return string.IsNullOrEmpty(text) ? null : new Person(text); - } - } - - public string Name { get; set; } - - public SemverVersion LatestVersion { get; set; } - - public IEnumerable AvailableVersions { - get { return _availableVersions; } - set { _availableVersions = value != null ? value.ToList() : new List(); } - } - - public IEnumerable Homepages { - get { - return _homepages; - } - } - - public void AddHomepage(string homepage) { - _homepages.Add(homepage); - } - - public void AppendToDescription(string text) { - _descriptionBuff.Append(text); - } - - public string Description { - get { - var text = _descriptionBuff.ToString().Trim(); - return string.IsNullOrEmpty(text) ? null : text; - } - } - - public void AppendToDate(string text) { - if (_publishDateTime.Length > 0) { - _publishDateTime.Append(' '); - } - _publishDateTime.Append(text); - } - - public string PublishDateTimeString { - get { - var text = _publishDateTime.ToString().Trim(); - return string.IsNullOrEmpty(text) ? null : text; - } - } - - public IEnumerable Dependencies { - get { return _dependencies; } - } - - public void AddDependency(IPackage module) { - _dependencies.Add(module); - } - - public void AddDependencies(IEnumerable packages) { - _dependencies.AddRange(packages); - } - - public PackageFlags Flags { get; set; } - - public string RequestedVersionRange { get; set; } - - public void AddKeyword(string keyword) { - _keywords.Add(keyword); - } - - public IEnumerable Keywords { - get { - return _keywords; - } - } - - public IPackage Build() { - var proxy = new PackageProxy { - Author = Author, - Name = Name, - Version = LatestVersion, - AvailableVersions = AvailableVersions, - Description = Description, - Homepages = Homepages, - PublishDateTimeString = PublishDateTimeString, - RequestedVersionRange = RequestedVersionRange, - Flags = Flags, - Keywords = _keywords - }; - - var modules = new NodeModulesProxy(); - foreach (var dep in Dependencies) { - modules.AddModule(dep); - } - proxy.Modules = modules; - return proxy; - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Emit; +using System.Text; +using Microsoft.NodejsTools.Npm.SPI; + +namespace Microsoft.NodejsTools.Npm { + /// + /// Mutable class for building immutable node module descriptions + /// + internal class NodeModuleBuilder { + private List _dependencies = new List(); + private readonly StringBuilder _descriptionBuff = new StringBuilder(); + private readonly StringBuilder _authorBuff = new StringBuilder(); + private readonly StringBuilder _publishDateTime = new StringBuilder(); + private List _keywords = new List(); + private List _homepages = new List(); + private List _availableVersions = new List(); + + public NodeModuleBuilder() { + Reset(); + } + + public void Reset() { + Name = null; + + // We should double check, but I believe that the package no longer exists when "latest" is not set. + // If that's the case, we should include an option to filter out those packages. + // https://nodejstools.codeplex.com/workitem/1452 + LatestVersion = SemverVersion.UnknownVersion; + _availableVersions = new List(); + + Flags = PackageFlags.None; + RequestedVersionRange = null; + + // These *have* to be reinitialised or they'll be cleared + // in any packages that have been created using the builder + // because they're passed by reference. + _dependencies = new List(); + _keywords = new List(); + _homepages = new List(); + + _descriptionBuff.Length = 0; + _authorBuff.Length = 0; + _publishDateTime.Length = 0; + } + + public void AddAuthor(string text) { + if (_authorBuff.Length > 0) { + _authorBuff.Append(' '); + } + _authorBuff.Append(text); + } + + public IPerson Author { + get { + var text = _authorBuff.ToString().Trim(); + return string.IsNullOrEmpty(text) ? null : new Person(text); + } + } + + public string Name { get; set; } + + public SemverVersion LatestVersion { get; set; } + + public IEnumerable AvailableVersions { + get { return _availableVersions; } + set { _availableVersions = value != null ? value.ToList() : new List(); } + } + + public IEnumerable Homepages { + get { + return _homepages; + } + } + + public void AddHomepage(string homepage) { + _homepages.Add(homepage); + } + + public void AppendToDescription(string text) { + _descriptionBuff.Append(text); + } + + public string Description { + get { + var text = _descriptionBuff.ToString().Trim(); + return string.IsNullOrEmpty(text) ? null : text; + } + } + + public void AppendToDate(string text) { + if (_publishDateTime.Length > 0) { + _publishDateTime.Append(' '); + } + _publishDateTime.Append(text); + } + + public string PublishDateTimeString { + get { + var text = _publishDateTime.ToString().Trim(); + return string.IsNullOrEmpty(text) ? null : text; + } + } + + public IEnumerable Dependencies { + get { return _dependencies; } + } + + public void AddDependency(IPackage module) { + _dependencies.Add(module); + } + + public void AddDependencies(IEnumerable packages) { + _dependencies.AddRange(packages); + } + + public PackageFlags Flags { get; set; } + + public string RequestedVersionRange { get; set; } + + public void AddKeyword(string keyword) { + _keywords.Add(keyword); + } + + public IEnumerable Keywords { + get { + return _keywords; + } + } + + public IPackage Build() { + var proxy = new PackageProxy { + Author = Author, + Name = Name, + Version = LatestVersion, + AvailableVersions = AvailableVersions, + Description = Description, + Homepages = Homepages, + PublishDateTimeString = PublishDateTimeString, + RequestedVersionRange = RequestedVersionRange, + Flags = Flags, + Keywords = _keywords + }; + + var modules = new NodeModulesProxy(); + foreach (var dep in Dependencies) { + modules.AddModule(dep); + } + proxy.Modules = modules; + return proxy; + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/NpmArgumentBuilder.cs b/Nodejs/Product/Npm/NpmArgumentBuilder.cs index b4870fede..b6798a086 100644 --- a/Nodejs/Product/Npm/NpmArgumentBuilder.cs +++ b/Nodejs/Product/Npm/NpmArgumentBuilder.cs @@ -1,53 +1,53 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public static class NpmArgumentBuilder { - public static string GetNpmInstallArguments(string packageName, - string versionRange, - DependencyType type, - bool global = false, - bool saveToPackageJson = true, - string otherArguments = "") - { - string dependencyArguments = ""; - if (global) { - dependencyArguments = "-g"; - } else if (saveToPackageJson) { - switch(type) { - case DependencyType.Standard: - dependencyArguments = "--save"; - break; - case DependencyType.Development: - dependencyArguments = "--save-dev"; - break; - case DependencyType.Optional: - dependencyArguments = "--save-optional"; - break; - } - } - - otherArguments = otherArguments.TrimStart(' ', '\t'); - if (otherArguments.StartsWith("@")) { - return string.Format("install {0}{1} {2}", packageName, otherArguments, dependencyArguments); - } else if (!string.IsNullOrEmpty(versionRange)) { - return string.Format("install {0}@\"{1}\" {2} {3}", packageName, versionRange, dependencyArguments, otherArguments); - } - - return string.Format("install {0} {1} {2}", packageName, dependencyArguments, otherArguments); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public static class NpmArgumentBuilder { + public static string GetNpmInstallArguments(string packageName, + string versionRange, + DependencyType type, + bool global = false, + bool saveToPackageJson = true, + string otherArguments = "") + { + string dependencyArguments = ""; + if (global) { + dependencyArguments = "-g"; + } else if (saveToPackageJson) { + switch(type) { + case DependencyType.Standard: + dependencyArguments = "--save"; + break; + case DependencyType.Development: + dependencyArguments = "--save-dev"; + break; + case DependencyType.Optional: + dependencyArguments = "--save-optional"; + break; + } + } + + otherArguments = otherArguments.TrimStart(' ', '\t'); + if (otherArguments.StartsWith("@")) { + return string.Format("install {0}{1} {2}", packageName, otherArguments, dependencyArguments); + } else if (!string.IsNullOrEmpty(versionRange)) { + return string.Format("install {0}@\"{1}\" {2} {3}", packageName, versionRange, dependencyArguments, otherArguments); + } + + return string.Format("install {0} {1} {2}", packageName, dependencyArguments, otherArguments); + } + } +} diff --git a/Nodejs/Product/Npm/NpmCatalogEmptyException.cs b/Nodejs/Product/Npm/NpmCatalogEmptyException.cs index e8f8a1b04..d9543c0b1 100644 --- a/Nodejs/Product/Npm/NpmCatalogEmptyException.cs +++ b/Nodejs/Product/Npm/NpmCatalogEmptyException.cs @@ -1,29 +1,29 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Runtime.Serialization; - -namespace Microsoft.NodejsTools.Npm { - - [Serializable] - public class NpmCatalogEmptyException : NpmExecutionException, ISerializable { - public NpmCatalogEmptyException(){} - public NpmCatalogEmptyException(string message) : base(message){} - public NpmCatalogEmptyException(string message, Exception innerException) : base(message, innerException){} - protected NpmCatalogEmptyException(SerializationInfo info, StreamingContext context) : base(info, context){} - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.NodejsTools.Npm { + + [Serializable] + public class NpmCatalogEmptyException : NpmExecutionException, ISerializable { + public NpmCatalogEmptyException(){} + public NpmCatalogEmptyException(string message) : base(message){} + public NpmCatalogEmptyException(string message, Exception innerException) : base(message, innerException){} + protected NpmCatalogEmptyException(SerializationInfo info, StreamingContext context) : base(info, context){} + } +} diff --git a/Nodejs/Product/Npm/NpmCommandCompletedEventArgs.cs b/Nodejs/Product/Npm/NpmCommandCompletedEventArgs.cs index 0284ef8bc..180bd6a54 100644 --- a/Nodejs/Product/Npm/NpmCommandCompletedEventArgs.cs +++ b/Nodejs/Product/Npm/NpmCommandCompletedEventArgs.cs @@ -1,52 +1,52 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - - /// - /// Fired when an attempt to execute an npm command is completed, whether - /// successfully or not. - /// - public class NpmCommandCompletedEventArgs : EventArgs { - public NpmCommandCompletedEventArgs(string arguments, bool withErrors, bool cancelled) { - Arguments = arguments; - WithErrors = withErrors; - Cancelled = cancelled; - } - - public string Arguments { get; private set; } - - public string CommandText { - get { return string.IsNullOrEmpty(Arguments) ? "npm" : string.Format("npm {0}", Arguments); } - } - - /// - /// Indicates whether or not there were errors whilst executing npm. - /// - public bool WithErrors { get; private set; } - - /// - /// Indicates whether or not the command was cancelled, with or without errors. - /// - public bool Cancelled { get; private set; } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + + /// + /// Fired when an attempt to execute an npm command is completed, whether + /// successfully or not. + /// + public class NpmCommandCompletedEventArgs : EventArgs { + public NpmCommandCompletedEventArgs(string arguments, bool withErrors, bool cancelled) { + Arguments = arguments; + WithErrors = withErrors; + Cancelled = cancelled; + } + + public string Arguments { get; private set; } + + public string CommandText { + get { return string.IsNullOrEmpty(Arguments) ? "npm" : string.Format("npm {0}", Arguments); } + } + + /// + /// Indicates whether or not there were errors whilst executing npm. + /// + public bool WithErrors { get; private set; } + + /// + /// Indicates whether or not the command was cancelled, with or without errors. + /// + public bool Cancelled { get; private set; } + } +} diff --git a/Nodejs/Product/Npm/NpmControllerFactory.cs b/Nodejs/Product/Npm/NpmControllerFactory.cs index df1f21d8f..c1616bc38 100644 --- a/Nodejs/Product/Npm/NpmControllerFactory.cs +++ b/Nodejs/Product/Npm/NpmControllerFactory.cs @@ -1,34 +1,34 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Microsoft.NodejsTools.Npm.SPI; - -namespace Microsoft.NodejsTools.Npm { - public class NpmControllerFactory { - public static INpmController Create( - string fullPathToRootPackageDirectory, - string cachePath, - bool showMissingDevOptionalSubPackages = false, - INpmPathProvider npmPathProvider = null, - bool useFallbackIfNpmNotFound = true) { - return new NpmController( - fullPathToRootPackageDirectory, - cachePath, - showMissingDevOptionalSubPackages, - npmPathProvider); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Microsoft.NodejsTools.Npm.SPI; + +namespace Microsoft.NodejsTools.Npm { + public class NpmControllerFactory { + public static INpmController Create( + string fullPathToRootPackageDirectory, + string cachePath, + bool showMissingDevOptionalSubPackages = false, + INpmPathProvider npmPathProvider = null, + bool useFallbackIfNpmNotFound = true) { + return new NpmController( + fullPathToRootPackageDirectory, + cachePath, + showMissingDevOptionalSubPackages, + npmPathProvider); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/NpmExceptionEventArgs.cs b/Nodejs/Product/Npm/NpmExceptionEventArgs.cs index c88e068b8..aeb0ac34f 100644 --- a/Nodejs/Product/Npm/NpmExceptionEventArgs.cs +++ b/Nodejs/Product/Npm/NpmExceptionEventArgs.cs @@ -1,31 +1,31 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - public class NpmExceptionEventArgs : EventArgs { - public NpmExceptionEventArgs(Exception cause) { - Exception = cause; - } - - public Exception Exception { get; private set; } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + public class NpmExceptionEventArgs : EventArgs { + public NpmExceptionEventArgs(Exception cause) { + Exception = cause; + } + + public Exception Exception { get; private set; } + } +} diff --git a/Nodejs/Product/Npm/NpmExecutionException.cs b/Nodejs/Product/Npm/NpmExecutionException.cs index 9100c6eac..38ed35e84 100644 --- a/Nodejs/Product/Npm/NpmExecutionException.cs +++ b/Nodejs/Product/Npm/NpmExecutionException.cs @@ -1,32 +1,32 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - [Serializable] - public class NpmExecutionException : Exception, ISerializable { - public NpmExecutionException() { } - public NpmExecutionException(string message) : base(message) { } - public NpmExecutionException(string message, Exception innerException) : base(message, innerException) { } - protected NpmExecutionException(SerializationInfo info, StreamingContext context) : base(info, context) { } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + [Serializable] + public class NpmExecutionException : Exception, ISerializable { + public NpmExecutionException() { } + public NpmExecutionException(string message) : base(message) { } + public NpmExecutionException(string message, Exception innerException) : base(message, innerException) { } + protected NpmExecutionException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Nodejs/Product/Npm/NpmLogEventArgs.cs b/Nodejs/Product/Npm/NpmLogEventArgs.cs index dff350efc..9234129d3 100644 --- a/Nodejs/Product/Npm/NpmLogEventArgs.cs +++ b/Nodejs/Product/Npm/NpmLogEventArgs.cs @@ -1,27 +1,27 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; - -namespace Microsoft.NodejsTools.Npm { - public class NpmLogEventArgs : EventArgs { - public NpmLogEventArgs(string logText) { - LogText = logText; - } - - public string LogText { get; private set; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; + +namespace Microsoft.NodejsTools.Npm { + public class NpmLogEventArgs : EventArgs { + public NpmLogEventArgs(string logText) { + LogText = logText; + } + + public string LogText { get; private set; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/NpmNotFoundException.cs b/Nodejs/Product/Npm/NpmNotFoundException.cs index cd8ea6c60..ec5d30194 100644 --- a/Nodejs/Product/Npm/NpmNotFoundException.cs +++ b/Nodejs/Product/Npm/NpmNotFoundException.cs @@ -1,33 +1,33 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - - [Serializable] - public class NpmNotFoundException : NpmExecutionException, ISerializable { - public NpmNotFoundException() { } - public NpmNotFoundException(string message) : base(message) { } - public NpmNotFoundException(string message, Exception innerException) : base(message, innerException) { } - protected NpmNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + + [Serializable] + public class NpmNotFoundException : NpmExecutionException, ISerializable { + public NpmNotFoundException() { } + public NpmNotFoundException(string message) : base(message) { } + public NpmNotFoundException(string message, Exception innerException) : base(message, innerException) { } + protected NpmNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Nodejs/Product/Npm/PackageComparer.cs b/Nodejs/Product/Npm/PackageComparer.cs index da3f71e65..c65583d45 100644 --- a/Nodejs/Product/Npm/PackageComparer.cs +++ b/Nodejs/Product/Npm/PackageComparer.cs @@ -1,33 +1,33 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm { - public class PackageComparer : IComparer { - public int Compare(IPackage x, IPackage y) { - if (x == y) { - return 0; - } else if (null == x) { - return -1; - } else if (null == y) { - return 1; - } - // TODO: should take into account versions! - return x.Name.CompareTo(y.Name); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm { + public class PackageComparer : IComparer { + public int Compare(IPackage x, IPackage y) { + if (x == y) { + return 0; + } else if (null == x) { + return -1; + } else if (null == y) { + return 1; + } + // TODO: should take into account versions! + return x.Name.CompareTo(y.Name); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/PackageFlags.cs b/Nodejs/Product/Npm/PackageFlags.cs index 65791ea3c..0c9a491b5 100644 --- a/Nodejs/Product/Npm/PackageFlags.cs +++ b/Nodejs/Product/Npm/PackageFlags.cs @@ -1,31 +1,31 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; - -namespace Microsoft.NodejsTools.Npm { - [Flags] - public enum PackageFlags { - None = 0x0000, - NotListedAsDependency = 0x0001, - Missing = 0x0002, - Dev = 0x0004, - Optional = 0x0008, - Bundled = 0x0010, - VersionMismatch = 0x0100, - Installed = 0x1000, - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; + +namespace Microsoft.NodejsTools.Npm { + [Flags] + public enum PackageFlags { + None = 0x0000, + NotListedAsDependency = 0x0001, + Missing = 0x0002, + Dev = 0x0004, + Optional = 0x0008, + Bundled = 0x0010, + VersionMismatch = 0x0100, + Installed = 0x1000, + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/PackageJsonException.cs b/Nodejs/Product/Npm/PackageJsonException.cs index 6823390b4..737d0ba4c 100644 --- a/Nodejs/Product/Npm/PackageJsonException.cs +++ b/Nodejs/Product/Npm/PackageJsonException.cs @@ -1,32 +1,32 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm { - [Serializable] - public class PackageJsonException : Exception, ISerializable { - public PackageJsonException() { } - public PackageJsonException(string message) : base(message) { } - public PackageJsonException(string message, Exception innerException) : base(message, innerException) { } - protected PackageJsonException(SerializationInfo info, StreamingContext context) : base(info, context) { } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm { + [Serializable] + public class PackageJsonException : Exception, ISerializable { + public PackageJsonException() { } + public PackageJsonException(string message) : base(message) { } + public PackageJsonException(string message, Exception innerException) : base(message, innerException) { } + protected PackageJsonException(SerializationInfo info, StreamingContext context) : base(info, context) { } + } +} diff --git a/Nodejs/Product/Npm/PackageJsonFactory.cs b/Nodejs/Product/Npm/PackageJsonFactory.cs index f92da4b35..d28f15634 100644 --- a/Nodejs/Product/Npm/PackageJsonFactory.cs +++ b/Nodejs/Product/Npm/PackageJsonFactory.cs @@ -1,25 +1,25 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Microsoft.NodejsTools.Npm.SPI; - -namespace Microsoft.NodejsTools.Npm { - public class PackageJsonFactory { - public static IPackageJson Create(IPackageJsonSource source) { - return null == source.Package ? null : new PackageJson(source.Package); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Microsoft.NodejsTools.Npm.SPI; + +namespace Microsoft.NodejsTools.Npm { + public class PackageJsonFactory { + public static IPackageJson Create(IPackageJsonSource source) { + return null == source.Package ? null : new PackageJson(source.Package); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/Properties/AssemblyInfo.cs b/Nodejs/Product/Npm/Properties/AssemblyInfo.cs index 464de5fe5..53754375a 100644 --- a/Nodejs/Product/Npm/Properties/AssemblyInfo.cs +++ b/Nodejs/Product/Npm/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Node.js Tools for Visual Studio - Npm")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("38de5856-defe-4dac-ab7b-43e5a733cfe5")] - -[assembly: InternalsVisibleTo("NpmTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Node.js Tools for Visual Studio - Npm")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("38de5856-defe-4dac-ab7b-43e5a733cfe5")] + +[assembly: InternalsVisibleTo("NpmTests, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")] diff --git a/Nodejs/Product/Npm/ReaderPackageJsonSource.cs b/Nodejs/Product/Npm/ReaderPackageJsonSource.cs index 57e0a7816..7889e2e91 100644 --- a/Nodejs/Product/Npm/ReaderPackageJsonSource.cs +++ b/Nodejs/Product/Npm/ReaderPackageJsonSource.cs @@ -1,65 +1,65 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.IO; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm { - public class ReaderPackageJsonSource : IPackageJsonSource { - public ReaderPackageJsonSource(TextReader reader) { - try { - var text = reader.ReadToEnd(); - try { - // JsonConvert and JObject.Parse exhibit slightly different behavior, - // so fall back to JObject.Parse if JsonConvert does not properly deserialize - // the object. - Package = JsonConvert.DeserializeObject(text); - } catch (ArgumentException) { - Package = JObject.Parse(text); - } - } catch (JsonReaderException jre) { - WrapExceptionAndRethrow(jre); - } catch (JsonSerializationException jse) { - WrapExceptionAndRethrow(jse); - } catch (FormatException fe) { - WrapExceptionAndRethrow(fe); - } catch (ArgumentException ae) { - throw new PackageJsonException( - string.Format(@"Error reading package.json. The file may be parseable JSON but may contain objects with duplicate properties. - -The following error occurred: - -{0}", ae.Message), - ae); - } - } - - private void WrapExceptionAndRethrow( - Exception ex) { - throw new PackageJsonException( - string.Format(@"Unable to read package.json. Please ensure the file is valid JSON. - -Reading failed because the following error occurred: - -{0}", ex.Message), - ex); - } - - public dynamic Package { get; private set; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.IO; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm { + public class ReaderPackageJsonSource : IPackageJsonSource { + public ReaderPackageJsonSource(TextReader reader) { + try { + var text = reader.ReadToEnd(); + try { + // JsonConvert and JObject.Parse exhibit slightly different behavior, + // so fall back to JObject.Parse if JsonConvert does not properly deserialize + // the object. + Package = JsonConvert.DeserializeObject(text); + } catch (ArgumentException) { + Package = JObject.Parse(text); + } + } catch (JsonReaderException jre) { + WrapExceptionAndRethrow(jre); + } catch (JsonSerializationException jse) { + WrapExceptionAndRethrow(jse); + } catch (FormatException fe) { + WrapExceptionAndRethrow(fe); + } catch (ArgumentException ae) { + throw new PackageJsonException( + string.Format(@"Error reading package.json. The file may be parseable JSON but may contain objects with duplicate properties. + +The following error occurred: + +{0}", ae.Message), + ae); + } + } + + private void WrapExceptionAndRethrow( + Exception ex) { + throw new PackageJsonException( + string.Format(@"Unable to read package.json. Please ensure the file is valid JSON. + +Reading failed because the following error occurred: + +{0}", ex.Message), + ex); + } + + public dynamic Package { get; private set; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/Resources.Designer.cs b/Nodejs/Product/Npm/Resources.Designer.cs index c811b2380..15c163ed8 100644 --- a/Nodejs/Product/Npm/Resources.Designer.cs +++ b/Nodejs/Product/Npm/Resources.Designer.cs @@ -1,297 +1,297 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.34014 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.NodejsTools.Npm { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.NodejsTools.Npm.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Cannot retrieve license from empty license collection.. - /// - internal static string CannotRetrieveLicenseEmptyCollection { - get { - return ResourceManager.GetString("CannotRetrieveLicenseEmptyCollection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot retrieve license for index less than 0.. - /// - internal static string CannotRetrieveLicenseInvalidIndex { - get { - return ResourceManager.GetString("CannotRetrieveLicenseInvalidIndex", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Database is corrupt - deleting {0}. Try refreshing the catalog again. Otherwise restart Visual Studio, and try again.. - /// - internal static string DatabaseCorrupt { - get { - return ResourceManager.GetString("DatabaseCorrupt", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Download or parsing failed - deleting {0}. Try refreshing the catalog again. Otherwise restart Visual Studio, and try again.. - /// - internal static string DownloadOrParsingFailed { - get { - return ResourceManager.GetString("DownloadOrParsingFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error executing npm - unable to start the npm process. - /// - internal static string ErrCannotStartNpm { - get { - return ResourceManager.GetString("ErrCannotStartNpm", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to retrieve npm catalog: the web service may be unavailable.. - /// - internal static string ErrNpmCatalogEmpty { - get { - return ResourceManager.GetString("ErrNpmCatalogEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Package catalog cache file is in use. Verify you are not refreshing the catalog in multiple instances.. - /// - internal static string ErrorCatalogInUse { - get { - return ResourceManager.GetString("ErrorCatalogInUse", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Executing command 'npm {0}'. - /// - internal static string ExecutingCommand { - get { - return ResourceManager.GetString("ExecutingCommand", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Could not delete file {0} - try deleting it manually.. - /// - internal static string FailedToDeleteFile { - get { - return ResourceManager.GetString("FailedToDeleteFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Your package catalog database needs to be upgraded. Please wait while we execute a full refresh.. - /// - internal static string InfoCatalogUpgrade { - get { - return ResourceManager.GetString("InfoCatalogUpgrade", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Current Time: {0}. - /// - internal static string InfoCurrentTime { - get { - return ResourceManager.GetString("InfoCurrentTime", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Deleting {0}. - /// - internal static string InfoDeletingFile { - get { - return ResourceManager.GetString("InfoDeletingFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Last Refreshed: {0}. - /// - internal static string InfoLastRefreshed { - get { - return ResourceManager.GetString("InfoLastRefreshed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Path to npm: {0}. - /// - internal static string InfoNpmPathLocation { - get { - return ResourceManager.GetString("InfoNpmPathLocation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Number of Results: {0}. - /// - internal static string InfoNumberOfResults { - get { - return ResourceManager.GetString("InfoNumberOfResults", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Downloading package cache to {0}. - /// - internal static string InfoPackageCacheWriteLocation { - get { - return ResourceManager.GetString("InfoPackageCacheWriteLocation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reading {0} bytes from {1} ({2}). - /// - internal static string InfoReadingBytesFromPackageCache { - get { - return ResourceManager.GetString("InfoReadingBytesFromPackageCache", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Registry url: {0}. - /// - internal static string InfoRegistryUrl { - get { - return ResourceManager.GetString("InfoRegistryUrl", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Semversion {0} for package {1} is invalid. - /// - internal static string InvalidPackageSemVersion { - get { - return ResourceManager.GetString("InvalidPackageSemVersion", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to npm command cancelled. - /// - internal static string NpmCommandCancelled { - get { - return ResourceManager.GetString("NpmCommandCancelled", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to npm command completed with exit code {0}. - /// - internal static string NpmCommandCompletedWithExitCode { - get { - return ResourceManager.GetString("NpmCommandCompletedWithExitCode", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Download complete - updating database. - /// - internal static string PackagesDownloadComplete { - get { - return ResourceManager.GetString("PackagesDownloadComplete", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Downloaded {0}MB of the package list. - /// - internal static string PackagesDownloadedXMB { - get { - return ResourceManager.GetString("PackagesDownloadedXMB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Downloaded {0}MB of the package list ({1}MB total). - /// - internal static string PackagesDownloadedXOfYMB { - get { - return ResourceManager.GetString("PackagesDownloadedXOfYMB", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Downloading list of packages from {0}. - /// - internal static string PackagesDownloadStarting { - get { - return ResourceManager.GetString("PackagesDownloadStarting", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Parsing error (package {0}). - /// - internal static string ParsingError { - get { - return ResourceManager.GetString("ParsingError", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.34014 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.NodejsTools.Npm { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.NodejsTools.Npm.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Cannot retrieve license from empty license collection.. + /// + internal static string CannotRetrieveLicenseEmptyCollection { + get { + return ResourceManager.GetString("CannotRetrieveLicenseEmptyCollection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot retrieve license for index less than 0.. + /// + internal static string CannotRetrieveLicenseInvalidIndex { + get { + return ResourceManager.GetString("CannotRetrieveLicenseInvalidIndex", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Database is corrupt - deleting {0}. Try refreshing the catalog again. Otherwise restart Visual Studio, and try again.. + /// + internal static string DatabaseCorrupt { + get { + return ResourceManager.GetString("DatabaseCorrupt", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Download or parsing failed - deleting {0}. Try refreshing the catalog again. Otherwise restart Visual Studio, and try again.. + /// + internal static string DownloadOrParsingFailed { + get { + return ResourceManager.GetString("DownloadOrParsingFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error executing npm - unable to start the npm process. + /// + internal static string ErrCannotStartNpm { + get { + return ResourceManager.GetString("ErrCannotStartNpm", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to retrieve npm catalog: the web service may be unavailable.. + /// + internal static string ErrNpmCatalogEmpty { + get { + return ResourceManager.GetString("ErrNpmCatalogEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Package catalog cache file is in use. Verify you are not refreshing the catalog in multiple instances.. + /// + internal static string ErrorCatalogInUse { + get { + return ResourceManager.GetString("ErrorCatalogInUse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Executing command 'npm {0}'. + /// + internal static string ExecutingCommand { + get { + return ResourceManager.GetString("ExecutingCommand", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Could not delete file {0} - try deleting it manually.. + /// + internal static string FailedToDeleteFile { + get { + return ResourceManager.GetString("FailedToDeleteFile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your package catalog database needs to be upgraded. Please wait while we execute a full refresh.. + /// + internal static string InfoCatalogUpgrade { + get { + return ResourceManager.GetString("InfoCatalogUpgrade", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Current Time: {0}. + /// + internal static string InfoCurrentTime { + get { + return ResourceManager.GetString("InfoCurrentTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Deleting {0}. + /// + internal static string InfoDeletingFile { + get { + return ResourceManager.GetString("InfoDeletingFile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Last Refreshed: {0}. + /// + internal static string InfoLastRefreshed { + get { + return ResourceManager.GetString("InfoLastRefreshed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Path to npm: {0}. + /// + internal static string InfoNpmPathLocation { + get { + return ResourceManager.GetString("InfoNpmPathLocation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Number of Results: {0}. + /// + internal static string InfoNumberOfResults { + get { + return ResourceManager.GetString("InfoNumberOfResults", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Downloading package cache to {0}. + /// + internal static string InfoPackageCacheWriteLocation { + get { + return ResourceManager.GetString("InfoPackageCacheWriteLocation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reading {0} bytes from {1} ({2}). + /// + internal static string InfoReadingBytesFromPackageCache { + get { + return ResourceManager.GetString("InfoReadingBytesFromPackageCache", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Registry url: {0}. + /// + internal static string InfoRegistryUrl { + get { + return ResourceManager.GetString("InfoRegistryUrl", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Semversion {0} for package {1} is invalid. + /// + internal static string InvalidPackageSemVersion { + get { + return ResourceManager.GetString("InvalidPackageSemVersion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to npm command cancelled. + /// + internal static string NpmCommandCancelled { + get { + return ResourceManager.GetString("NpmCommandCancelled", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to npm command completed with exit code {0}. + /// + internal static string NpmCommandCompletedWithExitCode { + get { + return ResourceManager.GetString("NpmCommandCompletedWithExitCode", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Download complete - updating database. + /// + internal static string PackagesDownloadComplete { + get { + return ResourceManager.GetString("PackagesDownloadComplete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Downloaded {0}MB of the package list. + /// + internal static string PackagesDownloadedXMB { + get { + return ResourceManager.GetString("PackagesDownloadedXMB", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Downloaded {0}MB of the package list ({1}MB total). + /// + internal static string PackagesDownloadedXOfYMB { + get { + return ResourceManager.GetString("PackagesDownloadedXOfYMB", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Downloading list of packages from {0}. + /// + internal static string PackagesDownloadStarting { + get { + return ResourceManager.GetString("PackagesDownloadStarting", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parsing error (package {0}). + /// + internal static string ParsingError { + get { + return ResourceManager.GetString("ParsingError", resourceCulture); + } + } + } +} diff --git a/Nodejs/Product/Npm/RootPackageFactory.cs b/Nodejs/Product/Npm/RootPackageFactory.cs index a4dbd4bee..c5693434d 100644 --- a/Nodejs/Product/Npm/RootPackageFactory.cs +++ b/Nodejs/Product/Npm/RootPackageFactory.cs @@ -1,35 +1,35 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Microsoft.NodejsTools.Npm.SPI; - -namespace Microsoft.NodejsTools.Npm { - public static class RootPackageFactory { - public static IRootPackage Create( - string fullPathToRootDirectory, - bool showMissingDevOptionalSubPackages = false) { - return new RootPackage( - fullPathToRootDirectory, - showMissingDevOptionalSubPackages); - } - - public static IGlobalPackages Create( - string fullPathToGlobalPackages) { - return new GlobalPackages( - fullPathToGlobalPackages); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Microsoft.NodejsTools.Npm.SPI; + +namespace Microsoft.NodejsTools.Npm { + public static class RootPackageFactory { + public static IRootPackage Create( + string fullPathToRootDirectory, + bool showMissingDevOptionalSubPackages = false) { + return new RootPackage( + fullPathToRootDirectory, + showMissingDevOptionalSubPackages); + } + + public static IGlobalPackages Create( + string fullPathToGlobalPackages) { + return new GlobalPackages( + fullPathToGlobalPackages); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/AbstractNodeModules.cs b/Nodejs/Product/Npm/SPI/AbstractNodeModules.cs index 9a562de81..2376ab697 100644 --- a/Nodejs/Product/Npm/SPI/AbstractNodeModules.cs +++ b/Nodejs/Product/Npm/SPI/AbstractNodeModules.cs @@ -1,74 +1,74 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections; -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal abstract class AbstractNodeModules : INodeModules { - protected readonly List _packagesSorted = new List(); - private readonly IDictionary _packagesByName = new Dictionary(); - - protected virtual void AddModule(IPackage package) { - if (package.Name != null && !_packagesByName.ContainsKey(package.Name)) { - _packagesSorted.Add(package); - _packagesByName[package.Name] = package; - } - } - - public int Count { - get { return _packagesSorted.Count; } - } - - public IPackage this[int index] { - get { return _packagesSorted[index]; } - } - - public IPackage this[string name] { - get { - IPackage pkg; - _packagesByName.TryGetValue(name, out pkg); - return pkg; - } - } - - public bool Contains(string name) { - return this[name] != null; - } - - public bool HasMissingModules { - get { - foreach (IPackage pkg in this) { - if (pkg.IsMissing) { - return true; - } - } - return false; - } - } - - public IEnumerator GetEnumerator() { - return _packagesSorted.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() { - return GetEnumerator(); - } - - public abstract int GetDepth(string filepath); - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal abstract class AbstractNodeModules : INodeModules { + protected readonly List _packagesSorted = new List(); + private readonly IDictionary _packagesByName = new Dictionary(); + + protected virtual void AddModule(IPackage package) { + if (package.Name != null && !_packagesByName.ContainsKey(package.Name)) { + _packagesSorted.Add(package); + _packagesByName[package.Name] = package; + } + } + + public int Count { + get { return _packagesSorted.Count; } + } + + public IPackage this[int index] { + get { return _packagesSorted[index]; } + } + + public IPackage this[string name] { + get { + IPackage pkg; + _packagesByName.TryGetValue(name, out pkg); + return pkg; + } + } + + public bool Contains(string name) { + return this[name] != null; + } + + public bool HasMissingModules { + get { + foreach (IPackage pkg in this) { + if (pkg.IsMissing) { + return true; + } + } + return false; + } + } + + public IEnumerator GetEnumerator() { + return _packagesSorted.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + public abstract int GetDepth(string filepath); + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/AbstractNpmLogSource.cs b/Nodejs/Product/Npm/SPI/AbstractNpmLogSource.cs index bad0c6a99..cc050076c 100644 --- a/Nodejs/Product/Npm/SPI/AbstractNpmLogSource.cs +++ b/Nodejs/Product/Npm/SPI/AbstractNpmLogSource.cs @@ -1,69 +1,69 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal abstract class AbstractNpmLogSource : INpmLogSource { - public event EventHandler CommandStarted; - - protected void OnCommandStarted() { - var handlers = CommandStarted; - if (null != handlers) { - handlers(this, EventArgs.Empty); - } - } - - protected void FireNpmLogEvent(string logText, EventHandler handlers) { - if (null != handlers && !string.IsNullOrEmpty(logText)) { - handlers(this, new NpmLogEventArgs(logText)); - } - } - - public event EventHandler OutputLogged; - - protected void OnOutputLogged(string logText) { - FireNpmLogEvent(logText, OutputLogged); - } - - public event EventHandler ErrorLogged; - - protected void OnErrorLogged(string logText) { - FireNpmLogEvent(logText, ErrorLogged); - } - - public event EventHandler ExceptionLogged; - - protected void OnExceptionLogged(Exception e) { - var handlers = ExceptionLogged; - if (null != handlers) { - handlers(this, new NpmExceptionEventArgs(e)); - } - } - - public event EventHandler CommandCompleted; - - protected void OnCommandCompleted( - string arguments, - bool withErrors, - bool cancelled) { - var handlers = CommandCompleted; - if (null != handlers) { - handlers(this, new NpmCommandCompletedEventArgs(arguments, withErrors, cancelled)); - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal abstract class AbstractNpmLogSource : INpmLogSource { + public event EventHandler CommandStarted; + + protected void OnCommandStarted() { + var handlers = CommandStarted; + if (null != handlers) { + handlers(this, EventArgs.Empty); + } + } + + protected void FireNpmLogEvent(string logText, EventHandler handlers) { + if (null != handlers && !string.IsNullOrEmpty(logText)) { + handlers(this, new NpmLogEventArgs(logText)); + } + } + + public event EventHandler OutputLogged; + + protected void OnOutputLogged(string logText) { + FireNpmLogEvent(logText, OutputLogged); + } + + public event EventHandler ErrorLogged; + + protected void OnErrorLogged(string logText) { + FireNpmLogEvent(logText, ErrorLogged); + } + + public event EventHandler ExceptionLogged; + + protected void OnExceptionLogged(Exception e) { + var handlers = ExceptionLogged; + if (null != handlers) { + handlers(this, new NpmExceptionEventArgs(e)); + } + } + + public event EventHandler CommandCompleted; + + protected void OnCommandCompleted( + string arguments, + bool withErrors, + bool cancelled) { + var handlers = CommandCompleted; + if (null != handlers) { + handlers(this, new NpmCommandCompletedEventArgs(arguments, withErrors, cancelled)); + } + } + } +} diff --git a/Nodejs/Product/Npm/SPI/AbstractNpmSearchComparer.cs b/Nodejs/Product/Npm/SPI/AbstractNpmSearchComparer.cs index e6ef1bf73..6ba98a408 100644 --- a/Nodejs/Product/Npm/SPI/AbstractNpmSearchComparer.cs +++ b/Nodejs/Product/Npm/SPI/AbstractNpmSearchComparer.cs @@ -1,36 +1,36 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal abstract class AbstractNpmSearchComparer : IComparer { - - protected int CompareBasedOnKeywords(IPackage x, IPackage y) { - if (x.Keywords != null && y.Keywords != null) { - return string.Compare( - string.Join(", ", x.Keywords), - string.Join(", ", y.Keywords), - StringComparison.CurrentCulture); - } - - return 0; - } - - public abstract int Compare(IPackage x, IPackage y); - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal abstract class AbstractNpmSearchComparer : IComparer { + + protected int CompareBasedOnKeywords(IPackage x, IPackage y) { + if (x.Keywords != null && y.Keywords != null) { + return string.Compare( + string.Join(", ", x.Keywords), + string.Join(", ", y.Keywords), + StringComparison.CurrentCulture); + } + + return 0; + } + + public abstract int Compare(IPackage x, IPackage y); + } +} diff --git a/Nodejs/Product/Npm/SPI/Bugs.cs b/Nodejs/Product/Npm/SPI/Bugs.cs index 784caa27a..29f86d84c 100644 --- a/Nodejs/Product/Npm/SPI/Bugs.cs +++ b/Nodejs/Product/Npm/SPI/Bugs.cs @@ -1,64 +1,64 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Bugs : IBugs { - private readonly dynamic _package; - - public Bugs(dynamic package) { - _package = package; - } - - - public string Url { - get { - string url = null; - var bugs = _package.bugs; - if (null != bugs) { - var token = bugs as JToken; - if (token.Type == JTokenType.Object) { - var temp = bugs.url ?? bugs.web; - if (null != temp) { - url = temp.ToString(); - } - } else { - url = token.Value(); - } - } - return url; - } - } - - public string Email { - get { - string email = null; - var bugs = _package.bugs; - if (null != bugs) { - var token = bugs as JToken; - if (token.Type == JTokenType.Object) { - var temp = bugs.email ?? bugs.mail; - if (null != temp) { - email = temp.ToString(); - } - } - } - return email; - } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Bugs : IBugs { + private readonly dynamic _package; + + public Bugs(dynamic package) { + _package = package; + } + + + public string Url { + get { + string url = null; + var bugs = _package.bugs; + if (null != bugs) { + var token = bugs as JToken; + if (token.Type == JTokenType.Object) { + var temp = bugs.url ?? bugs.web; + if (null != temp) { + url = temp.ToString(); + } + } else { + url = token.Value(); + } + } + return url; + } + } + + public string Email { + get { + string email = null; + var bugs = _package.bugs; + if (null != bugs) { + var token = bugs as JToken; + if (token.Type == JTokenType.Object) { + var temp = bugs.email ?? bugs.mail; + if (null != temp) { + email = temp.ToString(); + } + } + } + return email; + } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/BundledDependencies.cs b/Nodejs/Product/Npm/SPI/BundledDependencies.cs index 46becffd8..31e0dd814 100644 --- a/Nodejs/Product/Npm/SPI/BundledDependencies.cs +++ b/Nodejs/Product/Npm/SPI/BundledDependencies.cs @@ -1,24 +1,24 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class BundledDependencies : PkgStringArray, IBundledDependencies { - public BundledDependencies(JObject package) - : base(package, "bundledDependencies", "bundleDependencies") { } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class BundledDependencies : PkgStringArray, IBundledDependencies { + public BundledDependencies(JObject package) + : base(package, "bundledDependencies", "bundleDependencies") { } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Dependencies.cs b/Nodejs/Product/Npm/SPI/Dependencies.cs index 284a00e47..b3bf42a6b 100644 --- a/Nodejs/Product/Npm/SPI/Dependencies.cs +++ b/Nodejs/Product/Npm/SPI/Dependencies.cs @@ -1,75 +1,75 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Dependencies : IDependencies { - private JObject _package; - private string[] _dependencyPropertyNames; - - public Dependencies(JObject package, params string[] dependencyPropertyNames) { - _package = package; - _dependencyPropertyNames = dependencyPropertyNames; - } - - private IEnumerable GetDependenciesProperties() { - foreach (var propertyName in _dependencyPropertyNames) { - var property = _package[propertyName] as JObject; - if (null != property) { - yield return property; - } - } - } - - public IEnumerator GetEnumerator() { - var dependencyProps = GetDependenciesProperties(); - foreach (var dependencies in dependencyProps) { - var properties = null == dependencies ? new List() : dependencies.Properties(); - foreach (var property in properties) { - yield return new Dependency(property.Name, property.Value.Value()); - } - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return GetEnumerator(); - } - - public int Count { - get { return this.Count(); } - } - - public IDependency this[string name] { - get { - foreach (var dependencies in GetDependenciesProperties()) { - var property = dependencies[name]; - if (null != property) { - return new Dependency(name, property.Value()); - } - } - return null; - } - } - - public bool Contains(string name) { - return this[name] != null; - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Dependencies : IDependencies { + private JObject _package; + private string[] _dependencyPropertyNames; + + public Dependencies(JObject package, params string[] dependencyPropertyNames) { + _package = package; + _dependencyPropertyNames = dependencyPropertyNames; + } + + private IEnumerable GetDependenciesProperties() { + foreach (var propertyName in _dependencyPropertyNames) { + var property = _package[propertyName] as JObject; + if (null != property) { + yield return property; + } + } + } + + public IEnumerator GetEnumerator() { + var dependencyProps = GetDependenciesProperties(); + foreach (var dependencies in dependencyProps) { + var properties = null == dependencies ? new List() : dependencies.Properties(); + foreach (var property in properties) { + yield return new Dependency(property.Name, property.Value.Value()); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + + public int Count { + get { return this.Count(); } + } + + public IDependency this[string name] { + get { + foreach (var dependencies in GetDependenciesProperties()) { + var property = dependencies[name]; + if (null != property) { + return new Dependency(name, property.Value()); + } + } + return null; + } + } + + public bool Contains(string name) { + return this[name] != null; + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Dependency.cs b/Nodejs/Product/Npm/SPI/Dependency.cs index 183090b55..94dba4f29 100644 --- a/Nodejs/Product/Npm/SPI/Dependency.cs +++ b/Nodejs/Product/Npm/SPI/Dependency.cs @@ -1,40 +1,40 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Dependency : IDependency { - private string _versionRangeUrlText; - - public Dependency(string name, string retreivalInfo) { - Name = name; - _versionRangeUrlText = retreivalInfo; - } - - public string Name { get; private set; } - - private bool IsVersionRange { - get { return _versionRangeUrlText.IndexOf('/') < 0; } - } - - public IDependencyUrl Url { - get { return IsVersionRange ? null : new DependencyUrl(_versionRangeUrlText); } - } - - public string VersionRangeText { - get { return IsVersionRange ? _versionRangeUrlText : null; } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Dependency : IDependency { + private string _versionRangeUrlText; + + public Dependency(string name, string retreivalInfo) { + Name = name; + _versionRangeUrlText = retreivalInfo; + } + + public string Name { get; private set; } + + private bool IsVersionRange { + get { return _versionRangeUrlText.IndexOf('/') < 0; } + } + + public IDependencyUrl Url { + get { return IsVersionRange ? null : new DependencyUrl(_versionRangeUrlText); } + } + + public string VersionRangeText { + get { return IsVersionRange ? _versionRangeUrlText : null; } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/DependencyUrl.cs b/Nodejs/Product/Npm/SPI/DependencyUrl.cs index c8796bee3..f406917a6 100644 --- a/Nodejs/Product/Npm/SPI/DependencyUrl.cs +++ b/Nodejs/Product/Npm/SPI/DependencyUrl.cs @@ -1,55 +1,55 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class DependencyUrl : IDependencyUrl { - public DependencyUrl(string address) { - Address = address; - } - - public string Address { get; private set; } - - public DependencyUrlType Type { - get { - var index = Address.IndexOf("://"); - if (index < 0) { - return DependencyUrlType.GitHub; - } else { - var prefix = Address.Substring(0, index); - switch (prefix) { - case "http": - return DependencyUrlType.Http; - - case "git": - return DependencyUrlType.Git; - - case "git+ssh": - return DependencyUrlType.GitSsh; - - case "git+http": - return DependencyUrlType.GitHttp; - - case "git+https": - return DependencyUrlType.GitHttps; - - default: - return DependencyUrlType.UnsupportedProtocol; - } - } - } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class DependencyUrl : IDependencyUrl { + public DependencyUrl(string address) { + Address = address; + } + + public string Address { get; private set; } + + public DependencyUrlType Type { + get { + var index = Address.IndexOf("://"); + if (index < 0) { + return DependencyUrlType.GitHub; + } else { + var prefix = Address.Substring(0, index); + switch (prefix) { + case "http": + return DependencyUrlType.Http; + + case "git": + return DependencyUrlType.Git; + + case "git+ssh": + return DependencyUrlType.GitSsh; + + case "git+http": + return DependencyUrlType.GitHttp; + + case "git+https": + return DependencyUrlType.GitHttps; + + default: + return DependencyUrlType.UnsupportedProtocol; + } + } + } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/GenericNpmCommand.cs b/Nodejs/Product/Npm/SPI/GenericNpmCommand.cs index 14f4d3b7e..a6eda0134 100644 --- a/Nodejs/Product/Npm/SPI/GenericNpmCommand.cs +++ b/Nodejs/Product/Npm/SPI/GenericNpmCommand.cs @@ -1,28 +1,28 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class GenericNpmCommand : NpmCommand { - public GenericNpmCommand( - string fullPathToRootPackageDirectory, - string arguments, - string pathToNpm = null) : base( - fullPathToRootPackageDirectory, - pathToNpm) { - Arguments = arguments; - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class GenericNpmCommand : NpmCommand { + public GenericNpmCommand( + string fullPathToRootPackageDirectory, + string arguments, + string pathToNpm = null) : base( + fullPathToRootPackageDirectory, + pathToNpm) { + Arguments = arguments; + } + } +} diff --git a/Nodejs/Product/Npm/SPI/GlobalPackages.cs b/Nodejs/Product/Npm/SPI/GlobalPackages.cs index cddf0b04e..382c6cbd9 100644 --- a/Nodejs/Product/Npm/SPI/GlobalPackages.cs +++ b/Nodejs/Product/Npm/SPI/GlobalPackages.cs @@ -1,21 +1,21 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class GlobalPackages : RootPackage, IGlobalPackages { - public GlobalPackages(string fullPathToRootDirectory) : base(fullPathToRootDirectory, false) { } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class GlobalPackages : RootPackage, IGlobalPackages { + public GlobalPackages(string fullPathToRootDirectory) : base(fullPathToRootDirectory, false) { } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Keywords.cs b/Nodejs/Product/Npm/SPI/Keywords.cs index 5b5f39314..853a53d34 100644 --- a/Nodejs/Product/Npm/SPI/Keywords.cs +++ b/Nodejs/Product/Npm/SPI/Keywords.cs @@ -1,23 +1,23 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Keywords : PkgStringArray, IKeywords { - public Keywords(JObject package) : base(package, "keywords") { } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Keywords : PkgStringArray, IKeywords { + public Keywords(JObject package) : base(package, "keywords") { } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/License.cs b/Nodejs/Product/Npm/SPI/License.cs index afd9327b9..71423f8dc 100644 --- a/Nodejs/Product/Npm/SPI/License.cs +++ b/Nodejs/Product/Npm/SPI/License.cs @@ -1,31 +1,31 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class License : ILicense { - public License(string type) { - Type = type; - } - - public License(string type, string url) - : this(type) { - Url = url; - } - - public string Type { get; private set; } - public string Url { get; private set; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class License : ILicense { + public License(string type) { + Type = type; + } + + public License(string type, string url) + : this(type) { + Url = url; + } + + public string Type { get; private set; } + public string Url { get; private set; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Licenses.cs b/Nodejs/Product/Npm/SPI/Licenses.cs index 97b951665..59197b57d 100644 --- a/Nodejs/Product/Npm/SPI/Licenses.cs +++ b/Nodejs/Product/Npm/SPI/Licenses.cs @@ -1,64 +1,64 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Licenses : ILicenses { - private dynamic _package; - - public Licenses(dynamic package) { - _package = package; - } - - public int Count { - get { - if (_package.license != null) { - return 1; - } - - var json = _package.licenses; - if (null == json) { - return 0; - } - - JArray array = json; - return array.Count; - } - } - - public ILicense this[int index] { - get { - if (index < 0) { - throw new IndexOutOfRangeException(Resources.CannotRetrieveLicenseInvalidIndex); - } - - if (index == 0 && _package.license != null) { - return new License(_package.license.ToString()); - } - - var json = _package.licenses; - if (null == json) { - throw new IndexOutOfRangeException(Resources.CannotRetrieveLicenseEmptyCollection); - } - - var lic = json[index]; - return new License(lic.type.ToString(), lic.url.ToString()); - } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Licenses : ILicenses { + private dynamic _package; + + public Licenses(dynamic package) { + _package = package; + } + + public int Count { + get { + if (_package.license != null) { + return 1; + } + + var json = _package.licenses; + if (null == json) { + return 0; + } + + JArray array = json; + return array.Count; + } + } + + public ILicense this[int index] { + get { + if (index < 0) { + throw new IndexOutOfRangeException(Resources.CannotRetrieveLicenseInvalidIndex); + } + + if (index == 0 && _package.license != null) { + return new License(_package.license.ToString()); + } + + var json = _package.licenses; + if (null == json) { + throw new IndexOutOfRangeException(Resources.CannotRetrieveLicenseEmptyCollection); + } + + var lic = json[index]; + return new License(lic.type.ToString(), lic.url.ToString()); + } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Man.cs b/Nodejs/Product/Npm/SPI/Man.cs index d2c20d29b..b882c3cc2 100644 --- a/Nodejs/Product/Npm/SPI/Man.cs +++ b/Nodejs/Product/Npm/SPI/Man.cs @@ -1,23 +1,23 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Man : PkgStringArray, IMan { - public Man(JObject package) : base(package, "man") { } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Man : PkgStringArray, IMan { + public Man(JObject package) : base(package, "man") { } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NodeModules.cs b/Nodejs/Product/Npm/SPI/NodeModules.cs index 661e05604..456605ac3 100644 --- a/Nodejs/Product/Npm/SPI/NodeModules.cs +++ b/Nodejs/Product/Npm/SPI/NodeModules.cs @@ -1,184 +1,184 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NodeModules : AbstractNodeModules { - private Dictionary _allModules; - private readonly string[] _ignoredDirectories = { @"\.bin", @"\.staging" }; - - public NodeModules(IRootPackage parent, bool showMissingDevOptionalSubPackages, Dictionary allModulesToDepth = null, int depth = 0) { - var modulesBase = Path.Combine(parent.Path, NodejsConstants.NodeModulesFolder); - - _allModules = allModulesToDepth ?? new Dictionary(); - - // This is the first time NodeModules is being created. - // Iterate through directories to add everything that's known to be top-level. - if (depth == 0) { - Debug.Assert(_allModules.Count == 0, "Depth is 0, but top-level modules have already been added."); - - IEnumerable topLevelDirectories = Enumerable.Empty(); - try { - topLevelDirectories = Directory.EnumerateDirectories(modulesBase); - } catch (IOException) { - // We want to handle DirectoryNotFound, DriveNotFound, PathTooLong - } catch (UnauthorizedAccessException) { - } - - // Go through every directory in node_modules, and see if it's required as a top-level dependency - foreach (var moduleDir in topLevelDirectories) { - if (moduleDir.Length < NativeMethods.MAX_FOLDER_PATH && !_ignoredDirectories.Any(toIgnore => moduleDir.EndsWith(toIgnore))) { - IPackageJson packageJson; - try { - packageJson = PackageJsonFactory.Create(new DirectoryPackageJsonSource(moduleDir)); - } catch (PackageJsonException) { - // Fail gracefully if there was an error parsing the package.json - Debug.Fail("Failed to parse package.json in {0}", moduleDir); - continue; - } - - if (packageJson != null) { - if (packageJson.RequiredBy.Count() > 0) { - // All dependencies in npm v3 will have at least one element present in _requiredBy. - // _requiredBy dependencies that begin with hash characters represent top-level dependencies - foreach (var requiredBy in packageJson.RequiredBy) { - if (requiredBy.StartsWith("#") || requiredBy == "/") { - AddTopLevelModule(parent, showMissingDevOptionalSubPackages, moduleDir, depth); - break; - } - } - } else { - // This dependency is a top-level dependency not added by npm v3 - AddTopLevelModule(parent, showMissingDevOptionalSubPackages, moduleDir, depth); - } - } - } - } - } - - if (modulesBase.Length < NativeMethods.MAX_FOLDER_PATH && parent.HasPackageJson) { - // Iterate through all dependencies in the root package.json - // Otherwise, only iterate through "dependencies" because iterating through optional, bundle, etc. dependencies - // becomes unmanageable when they are already installed at the root of the project, and the performance impact - // typically isn't worth the value add. - var dependencies = depth == 0 ? parent.PackageJson.AllDependencies : parent.PackageJson.Dependencies; - foreach (var dependency in dependencies) { - var moduleDir = modulesBase; - - // try to find folder by recursing up tree - do { - moduleDir = Path.Combine(moduleDir, dependency.Name); - if (AddModuleIfNotExists(parent, moduleDir, showMissingDevOptionalSubPackages, depth, dependency)) { - break; - } - - var parentNodeModulesIndex = moduleDir.LastIndexOf(NodejsConstants.NodeModulesFolder, Math.Max(0, moduleDir.Length - NodejsConstants.NodeModulesFolder.Length - dependency.Name.Length - 1)); - moduleDir = moduleDir.Substring(0, parentNodeModulesIndex + NodejsConstants.NodeModulesFolder.Length); - } while (moduleDir.Contains(NodejsConstants.NodeModulesFolder)); - } - } - - _packagesSorted.Sort(new PackageComparer()); - } - - private void AddTopLevelModule(IRootPackage parent, bool showMissingDevOptionalSubPackages, string moduleDir, int depth) { - Debug.Assert(depth == 0, "Depth should be 0 when adding a top level dependency"); - AddModuleIfNotExists(parent, moduleDir, showMissingDevOptionalSubPackages, depth); - } - - private bool AddModuleIfNotExists(IRootPackage parent, string moduleDir, bool showMissingDevOptionalSubPackages, int depth, IDependency dependency = null) { - depth++; - - ModuleInfo moduleInfo; - _allModules.TryGetValue(moduleDir, out moduleInfo); - - if (moduleInfo != null) { - // Update module information if the module already exists. - if (moduleInfo.Depth > depth) { - moduleInfo.Depth = depth; - } - - if (dependency != null) { - var existingPackage = this[dependency.Name] as Package; - if (existingPackage != null) { - existingPackage.RequestedVersionRange = dependency.VersionRangeText; - } - } - } else if (Directory.Exists(moduleDir) || depth == 1) { - // Top-level modules are always added so we can include missing modules. - moduleInfo = new ModuleInfo(depth); - _allModules.Add(moduleDir, moduleInfo); - } else { - // The module directory wasn't found. - return false; - } - - IPackage package = moduleInfo.Package; - - if (package == null || depth == 1 || !moduleInfo.RequiredBy.Contains(parent.Path)) { - // Create a dummy value for the current package to prevent infinite loops - moduleInfo.Package = new PackageProxy(); - - moduleInfo.RequiredBy.Add(parent.Path); - - var pkg = new Package(parent, moduleDir, showMissingDevOptionalSubPackages, _allModules, depth); - if (dependency != null) { - pkg.RequestedVersionRange = dependency.VersionRangeText; - } - - package = moduleInfo.Package = pkg; - } - - if (parent as IPackage == null || !package.IsMissing || showMissingDevOptionalSubPackages) { - AddModule(package); - } - - return true; - } - - public override int GetDepth(string filepath) { - var lastNodeModules = filepath.LastIndexOf(NodejsConstants.NodeModulesFolder + "\\"); - var directoryToSearch = filepath.IndexOf("\\", lastNodeModules + NodejsConstants.NodeModulesFolder.Length + 1); - var directorySubString = directoryToSearch == -1 ? filepath : filepath.Substring(0, directoryToSearch); - - ModuleInfo value = null; - _allModules.TryGetValue(directorySubString, out value); - - var depth = value != null ? value.Depth : 0; - Debug.WriteLine("Module Depth: {0} [{1}]", filepath, depth); - - return depth; - } - } - - internal class ModuleInfo { - public int Depth { get; set; } - - public IPackage Package { get; set; } - - public IList RequiredBy { get; set; } - - internal ModuleInfo(int depth) { - Depth = depth; - RequiredBy = new List(); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NodeModules : AbstractNodeModules { + private Dictionary _allModules; + private readonly string[] _ignoredDirectories = { @"\.bin", @"\.staging" }; + + public NodeModules(IRootPackage parent, bool showMissingDevOptionalSubPackages, Dictionary allModulesToDepth = null, int depth = 0) { + var modulesBase = Path.Combine(parent.Path, NodejsConstants.NodeModulesFolder); + + _allModules = allModulesToDepth ?? new Dictionary(); + + // This is the first time NodeModules is being created. + // Iterate through directories to add everything that's known to be top-level. + if (depth == 0) { + Debug.Assert(_allModules.Count == 0, "Depth is 0, but top-level modules have already been added."); + + IEnumerable topLevelDirectories = Enumerable.Empty(); + try { + topLevelDirectories = Directory.EnumerateDirectories(modulesBase); + } catch (IOException) { + // We want to handle DirectoryNotFound, DriveNotFound, PathTooLong + } catch (UnauthorizedAccessException) { + } + + // Go through every directory in node_modules, and see if it's required as a top-level dependency + foreach (var moduleDir in topLevelDirectories) { + if (moduleDir.Length < NativeMethods.MAX_FOLDER_PATH && !_ignoredDirectories.Any(toIgnore => moduleDir.EndsWith(toIgnore))) { + IPackageJson packageJson; + try { + packageJson = PackageJsonFactory.Create(new DirectoryPackageJsonSource(moduleDir)); + } catch (PackageJsonException) { + // Fail gracefully if there was an error parsing the package.json + Debug.Fail("Failed to parse package.json in {0}", moduleDir); + continue; + } + + if (packageJson != null) { + if (packageJson.RequiredBy.Count() > 0) { + // All dependencies in npm v3 will have at least one element present in _requiredBy. + // _requiredBy dependencies that begin with hash characters represent top-level dependencies + foreach (var requiredBy in packageJson.RequiredBy) { + if (requiredBy.StartsWith("#") || requiredBy == "/") { + AddTopLevelModule(parent, showMissingDevOptionalSubPackages, moduleDir, depth); + break; + } + } + } else { + // This dependency is a top-level dependency not added by npm v3 + AddTopLevelModule(parent, showMissingDevOptionalSubPackages, moduleDir, depth); + } + } + } + } + } + + if (modulesBase.Length < NativeMethods.MAX_FOLDER_PATH && parent.HasPackageJson) { + // Iterate through all dependencies in the root package.json + // Otherwise, only iterate through "dependencies" because iterating through optional, bundle, etc. dependencies + // becomes unmanageable when they are already installed at the root of the project, and the performance impact + // typically isn't worth the value add. + var dependencies = depth == 0 ? parent.PackageJson.AllDependencies : parent.PackageJson.Dependencies; + foreach (var dependency in dependencies) { + var moduleDir = modulesBase; + + // try to find folder by recursing up tree + do { + moduleDir = Path.Combine(moduleDir, dependency.Name); + if (AddModuleIfNotExists(parent, moduleDir, showMissingDevOptionalSubPackages, depth, dependency)) { + break; + } + + var parentNodeModulesIndex = moduleDir.LastIndexOf(NodejsConstants.NodeModulesFolder, Math.Max(0, moduleDir.Length - NodejsConstants.NodeModulesFolder.Length - dependency.Name.Length - 1)); + moduleDir = moduleDir.Substring(0, parentNodeModulesIndex + NodejsConstants.NodeModulesFolder.Length); + } while (moduleDir.Contains(NodejsConstants.NodeModulesFolder)); + } + } + + _packagesSorted.Sort(new PackageComparer()); + } + + private void AddTopLevelModule(IRootPackage parent, bool showMissingDevOptionalSubPackages, string moduleDir, int depth) { + Debug.Assert(depth == 0, "Depth should be 0 when adding a top level dependency"); + AddModuleIfNotExists(parent, moduleDir, showMissingDevOptionalSubPackages, depth); + } + + private bool AddModuleIfNotExists(IRootPackage parent, string moduleDir, bool showMissingDevOptionalSubPackages, int depth, IDependency dependency = null) { + depth++; + + ModuleInfo moduleInfo; + _allModules.TryGetValue(moduleDir, out moduleInfo); + + if (moduleInfo != null) { + // Update module information if the module already exists. + if (moduleInfo.Depth > depth) { + moduleInfo.Depth = depth; + } + + if (dependency != null) { + var existingPackage = this[dependency.Name] as Package; + if (existingPackage != null) { + existingPackage.RequestedVersionRange = dependency.VersionRangeText; + } + } + } else if (Directory.Exists(moduleDir) || depth == 1) { + // Top-level modules are always added so we can include missing modules. + moduleInfo = new ModuleInfo(depth); + _allModules.Add(moduleDir, moduleInfo); + } else { + // The module directory wasn't found. + return false; + } + + IPackage package = moduleInfo.Package; + + if (package == null || depth == 1 || !moduleInfo.RequiredBy.Contains(parent.Path)) { + // Create a dummy value for the current package to prevent infinite loops + moduleInfo.Package = new PackageProxy(); + + moduleInfo.RequiredBy.Add(parent.Path); + + var pkg = new Package(parent, moduleDir, showMissingDevOptionalSubPackages, _allModules, depth); + if (dependency != null) { + pkg.RequestedVersionRange = dependency.VersionRangeText; + } + + package = moduleInfo.Package = pkg; + } + + if (parent as IPackage == null || !package.IsMissing || showMissingDevOptionalSubPackages) { + AddModule(package); + } + + return true; + } + + public override int GetDepth(string filepath) { + var lastNodeModules = filepath.LastIndexOf(NodejsConstants.NodeModulesFolder + "\\"); + var directoryToSearch = filepath.IndexOf("\\", lastNodeModules + NodejsConstants.NodeModulesFolder.Length + 1); + var directorySubString = directoryToSearch == -1 ? filepath : filepath.Substring(0, directoryToSearch); + + ModuleInfo value = null; + _allModules.TryGetValue(directorySubString, out value); + + var depth = value != null ? value.Depth : 0; + Debug.WriteLine("Module Depth: {0} [{1}]", filepath, depth); + + return depth; + } + } + + internal class ModuleInfo { + public int Depth { get; set; } + + public IPackage Package { get; set; } + + public IList RequiredBy { get; set; } + + internal ModuleInfo(int depth) { + Depth = depth; + RequiredBy = new List(); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NodeModulesProxy.cs b/Nodejs/Product/Npm/SPI/NodeModulesProxy.cs index 052fa0ebc..0ba052dde 100644 --- a/Nodejs/Product/Npm/SPI/NodeModulesProxy.cs +++ b/Nodejs/Product/Npm/SPI/NodeModulesProxy.cs @@ -1,29 +1,29 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NodeModulesProxy : AbstractNodeModules { - public new void AddModule(IPackage package) { - base.AddModule(package); - } - - public override int GetDepth(string filepath) { - throw new NotImplementedException(); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NodeModulesProxy : AbstractNodeModules { + public new void AddModule(IPackage package) { + base.AddModule(package); + } + + public override int GetDepth(string filepath) { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NpmCommand.cs b/Nodejs/Product/Npm/SPI/NpmCommand.cs index ff1ae985b..d5e40a870 100644 --- a/Nodejs/Product/Npm/SPI/NpmCommand.cs +++ b/Nodejs/Product/Npm/SPI/NpmCommand.cs @@ -1,134 +1,134 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.IO; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.VisualStudioTools.Project; -using Microsoft.Win32; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal abstract class NpmCommand : AbstractNpmLogSource { - private readonly string _fullPathToRootPackageDirectory; - private string _pathToNpm; - private readonly ManualResetEvent _cancellation; - private readonly StringBuilder _output = new StringBuilder(); - private StringBuilder _error = new StringBuilder(); - private readonly object _bufferLock = new object(); - - protected NpmCommand( - string fullPathToRootPackageDirectory, - string pathToNpm = null) { - _fullPathToRootPackageDirectory = fullPathToRootPackageDirectory; - _pathToNpm = pathToNpm; - _cancellation = new ManualResetEvent(false); - } - - protected string Arguments { get; set; } - - internal string FullPathToRootPackageDirectory { - get { return _fullPathToRootPackageDirectory; } - } - - protected string GetPathToNpm() { - if (null == _pathToNpm || !File.Exists(_pathToNpm)) { - _pathToNpm = NpmHelpers.GetPathToNpm(); - } - return _pathToNpm; - } - - public string StandardOutput { - get { - lock (_bufferLock) { - return _output.ToString(); - } - } - } - - public string StandardError { - get { - lock (_bufferLock) { - return _error.ToString(); - } - } - } - - public void CancelCurrentTask() { - _cancellation.Set(); - } - - public virtual async Task ExecuteAsync() { - OnCommandStarted(); - try { - GetPathToNpm(); - } catch (NpmNotFoundException) { - return false; - } - var redirector = new NpmCommandRedirector(this); - redirector.WriteLine( - string.Format("===={0}====\r\n\r\n", - string.Format(Resources.ExecutingCommand, Arguments))); - - var cancelled = false; - try { - await NpmHelpers.ExecuteNpmCommandAsync( - redirector, - GetPathToNpm(), - _fullPathToRootPackageDirectory, - new[] { Arguments }, - _cancellation); - } catch (OperationCanceledException) { - cancelled = true; - } - OnCommandCompleted(Arguments, redirector.HasErrors, cancelled); - return !redirector.HasErrors; - } - - internal class NpmCommandRedirector : Redirector { - NpmCommand _owner; - - public NpmCommandRedirector(NpmCommand owner) { - _owner = owner; - } - - public bool HasErrors { get; private set; } - - private string AppendToBuffer(StringBuilder buffer, string data) { - if (data != null) { - lock (_owner._bufferLock) { - buffer.Append(data + Environment.NewLine); - } - } - return data; - } - - public override void WriteLine(string line) { - _owner.OnOutputLogged(AppendToBuffer(_owner._output, line)); - } - - public override void WriteErrorLine(string line) { - HasErrors = true; - _owner.OnErrorLogged(AppendToBuffer(_owner._error, line)); - } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.VisualStudioTools.Project; +using Microsoft.Win32; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal abstract class NpmCommand : AbstractNpmLogSource { + private readonly string _fullPathToRootPackageDirectory; + private string _pathToNpm; + private readonly ManualResetEvent _cancellation; + private readonly StringBuilder _output = new StringBuilder(); + private StringBuilder _error = new StringBuilder(); + private readonly object _bufferLock = new object(); + + protected NpmCommand( + string fullPathToRootPackageDirectory, + string pathToNpm = null) { + _fullPathToRootPackageDirectory = fullPathToRootPackageDirectory; + _pathToNpm = pathToNpm; + _cancellation = new ManualResetEvent(false); + } + + protected string Arguments { get; set; } + + internal string FullPathToRootPackageDirectory { + get { return _fullPathToRootPackageDirectory; } + } + + protected string GetPathToNpm() { + if (null == _pathToNpm || !File.Exists(_pathToNpm)) { + _pathToNpm = NpmHelpers.GetPathToNpm(); + } + return _pathToNpm; + } + + public string StandardOutput { + get { + lock (_bufferLock) { + return _output.ToString(); + } + } + } + + public string StandardError { + get { + lock (_bufferLock) { + return _error.ToString(); + } + } + } + + public void CancelCurrentTask() { + _cancellation.Set(); + } + + public virtual async Task ExecuteAsync() { + OnCommandStarted(); + try { + GetPathToNpm(); + } catch (NpmNotFoundException) { + return false; + } + var redirector = new NpmCommandRedirector(this); + redirector.WriteLine( + string.Format("===={0}====\r\n\r\n", + string.Format(Resources.ExecutingCommand, Arguments))); + + var cancelled = false; + try { + await NpmHelpers.ExecuteNpmCommandAsync( + redirector, + GetPathToNpm(), + _fullPathToRootPackageDirectory, + new[] { Arguments }, + _cancellation); + } catch (OperationCanceledException) { + cancelled = true; + } + OnCommandCompleted(Arguments, redirector.HasErrors, cancelled); + return !redirector.HasErrors; + } + + internal class NpmCommandRedirector : Redirector { + NpmCommand _owner; + + public NpmCommandRedirector(NpmCommand owner) { + _owner = owner; + } + + public bool HasErrors { get; private set; } + + private string AppendToBuffer(StringBuilder buffer, string data) { + if (data != null) { + lock (_owner._bufferLock) { + buffer.Append(data + Environment.NewLine); + } + } + return data; + } + + public override void WriteLine(string line) { + _owner.OnOutputLogged(AppendToBuffer(_owner._output, line)); + } + + public override void WriteErrorLine(string line) { + HasErrors = true; + _owner.OnErrorLogged(AppendToBuffer(_owner._error, line)); + } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NpmCommander.cs b/Nodejs/Product/Npm/SPI/NpmCommander.cs index 133373a4b..86130297c 100644 --- a/Nodejs/Product/Npm/SPI/NpmCommander.cs +++ b/Nodejs/Product/Npm/SPI/NpmCommander.cs @@ -1,219 +1,219 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NpmCommander : AbstractNpmLogSource, INpmCommander { - private NpmController _npmController; - private NpmCommand _command; - private bool _disposed; - - public NpmCommander(NpmController controller) { - _npmController = controller; - CommandStarted += _npmController.LogCommandStarted; - OutputLogged += _npmController.LogOutput; - ErrorLogged += _npmController.LogError; - ExceptionLogged += _npmController.LogException; - CommandCompleted += _npmController.LogCommandCompleted; - } - - public void Dispose() { - if (!_disposed) { - _disposed = true; - CommandStarted -= _npmController.LogCommandStarted; - OutputLogged -= _npmController.LogOutput; - ErrorLogged -= _npmController.LogError; - ExceptionLogged -= _npmController.LogException; - CommandCompleted -= _npmController.LogCommandCompleted; - } - } - - public void CancelCurrentCommand() { - if (null != _command) { - _command.CancelCurrentTask(); - } - } - - private void command_CommandStarted(object sender, EventArgs e) { - OnCommandStarted(); - } - - private void command_ExceptionLogged(object sender, NpmExceptionEventArgs e) { - OnExceptionLogged(e.Exception); - } - - private void command_ErrorLogged(object sender, NpmLogEventArgs e) { - OnErrorLogged(e.LogText); - } - - private void command_OutputLogged(object sender, NpmLogEventArgs e) { - OnOutputLogged(e.LogText); - } - - private void command_CommandCompleted(object sender, NpmCommandCompletedEventArgs e) { - OnCommandCompleted(e.Arguments, e.WithErrors, e.Cancelled); - } - - private void RegisterLogEvents(NpmCommand command) { - command.CommandStarted += command_CommandStarted; - command.OutputLogged += command_OutputLogged; - command.CommandCompleted += command_CommandCompleted; - - command.ErrorLogged += command_ErrorLogged; - command.ExceptionLogged += command_ExceptionLogged; - } - - private void UnregisterLogEvents(NpmCommand command) { - command.CommandStarted -= command_CommandStarted; - command.OutputLogged -= command_OutputLogged; - command.CommandCompleted -= command_CommandCompleted; - - command.ErrorLogged -= command_ErrorLogged; - command.ExceptionLogged -= command_ExceptionLogged; - } - - private async Task DoCommandExecute(bool refreshNpmController) { - try { - RegisterLogEvents(_command); - bool success = await _command.ExecuteAsync(); - UnregisterLogEvents(_command); - if (refreshNpmController) { - _npmController.Refresh(); - } - return success; - } catch (Exception e) { - OnOutputLogged(e.ToString()); - } - return false; - - } - - public async Task Install() { - _command = new NpmInstallCommand( - _npmController.FullPathToRootPackageDirectory, - _npmController.PathToNpm); - return await DoCommandExecute(true); - } - - private async Task InstallPackageByVersionAsync( - string packageName, - string versionRange, - DependencyType type, - bool global, - bool saveToPackageJson) { - _command = new NpmInstallCommand( - _npmController.FullPathToRootPackageDirectory, - packageName, - versionRange, - type, - global, - saveToPackageJson, - _npmController.PathToNpm); - return await DoCommandExecute(true); - } - - public async Task InstallPackageByVersionAsync( - string packageName, - string versionRange, - DependencyType type, - bool saveToPackageJson) { - return await InstallPackageByVersionAsync(packageName, versionRange, type, false, saveToPackageJson); - } - - public async Task InstallGlobalPackageByVersionAsync(string packageName, string versionRange) { - return await InstallPackageByVersionAsync(packageName, versionRange, DependencyType.Standard, true, false); - } - - private DependencyType GetDependencyType(string packageName) { - var type = DependencyType.Standard; - var root = _npmController.RootPackage; - if (null != root) { - var match = root.Modules[packageName]; - if (null != match) { - if (match.IsDevDependency) { - type = DependencyType.Development; - } else if (match.IsOptionalDependency) { - type = DependencyType.Optional; - } - } - } - return type; - } - - private async Task UninstallPackageAsync(string packageName, bool global) { - _command = new NpmUninstallCommand( - _npmController.FullPathToRootPackageDirectory, - packageName, - GetDependencyType(packageName), - global, - _npmController.PathToNpm); - return await DoCommandExecute(true); - } - - public async Task UninstallPackageAsync(string packageName) { - return await UninstallPackageAsync(packageName, false); - } - - public async Task UninstallGlobalPackageAsync(string packageName) { - return await UninstallPackageAsync(packageName, true); - } - - public async Task GetCatalogAsync(bool forceDownload, IProgress progress) { - _command = new NpmGetCatalogCommand( - _npmController.FullPathToRootPackageDirectory, - _npmController.CachePath, - forceDownload, - pathToNpm:_npmController.PathToNpm, - progress: progress); - await DoCommandExecute(false); - return (_command as NpmGetCatalogCommand).Catalog; - } - - public async Task UpdatePackagesAsync() { - return await UpdatePackagesAsync(new List()); - } - - private async Task UpdatePackagesAsync(IEnumerable packages, bool global) { - _command = new NpmUpdateCommand( - _npmController.FullPathToRootPackageDirectory, - packages, - global, - _npmController.PathToNpm); - return await DoCommandExecute(true); - } - - public async Task UpdatePackagesAsync(IEnumerable packages) { - return await UpdatePackagesAsync(packages, false); - } - - public async Task UpdateGlobalPackagesAsync(IEnumerable packages) { - return await UpdatePackagesAsync(packages, true); - } - - public async Task ExecuteNpmCommandAsync(string arguments) { - _command = new GenericNpmCommand( - _npmController.FullPathToRootPackageDirectory, - arguments, - _npmController.PathToNpm); - return await DoCommandExecute(true); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NpmCommander : AbstractNpmLogSource, INpmCommander { + private NpmController _npmController; + private NpmCommand _command; + private bool _disposed; + + public NpmCommander(NpmController controller) { + _npmController = controller; + CommandStarted += _npmController.LogCommandStarted; + OutputLogged += _npmController.LogOutput; + ErrorLogged += _npmController.LogError; + ExceptionLogged += _npmController.LogException; + CommandCompleted += _npmController.LogCommandCompleted; + } + + public void Dispose() { + if (!_disposed) { + _disposed = true; + CommandStarted -= _npmController.LogCommandStarted; + OutputLogged -= _npmController.LogOutput; + ErrorLogged -= _npmController.LogError; + ExceptionLogged -= _npmController.LogException; + CommandCompleted -= _npmController.LogCommandCompleted; + } + } + + public void CancelCurrentCommand() { + if (null != _command) { + _command.CancelCurrentTask(); + } + } + + private void command_CommandStarted(object sender, EventArgs e) { + OnCommandStarted(); + } + + private void command_ExceptionLogged(object sender, NpmExceptionEventArgs e) { + OnExceptionLogged(e.Exception); + } + + private void command_ErrorLogged(object sender, NpmLogEventArgs e) { + OnErrorLogged(e.LogText); + } + + private void command_OutputLogged(object sender, NpmLogEventArgs e) { + OnOutputLogged(e.LogText); + } + + private void command_CommandCompleted(object sender, NpmCommandCompletedEventArgs e) { + OnCommandCompleted(e.Arguments, e.WithErrors, e.Cancelled); + } + + private void RegisterLogEvents(NpmCommand command) { + command.CommandStarted += command_CommandStarted; + command.OutputLogged += command_OutputLogged; + command.CommandCompleted += command_CommandCompleted; + + command.ErrorLogged += command_ErrorLogged; + command.ExceptionLogged += command_ExceptionLogged; + } + + private void UnregisterLogEvents(NpmCommand command) { + command.CommandStarted -= command_CommandStarted; + command.OutputLogged -= command_OutputLogged; + command.CommandCompleted -= command_CommandCompleted; + + command.ErrorLogged -= command_ErrorLogged; + command.ExceptionLogged -= command_ExceptionLogged; + } + + private async Task DoCommandExecute(bool refreshNpmController) { + try { + RegisterLogEvents(_command); + bool success = await _command.ExecuteAsync(); + UnregisterLogEvents(_command); + if (refreshNpmController) { + _npmController.Refresh(); + } + return success; + } catch (Exception e) { + OnOutputLogged(e.ToString()); + } + return false; + + } + + public async Task Install() { + _command = new NpmInstallCommand( + _npmController.FullPathToRootPackageDirectory, + _npmController.PathToNpm); + return await DoCommandExecute(true); + } + + private async Task InstallPackageByVersionAsync( + string packageName, + string versionRange, + DependencyType type, + bool global, + bool saveToPackageJson) { + _command = new NpmInstallCommand( + _npmController.FullPathToRootPackageDirectory, + packageName, + versionRange, + type, + global, + saveToPackageJson, + _npmController.PathToNpm); + return await DoCommandExecute(true); + } + + public async Task InstallPackageByVersionAsync( + string packageName, + string versionRange, + DependencyType type, + bool saveToPackageJson) { + return await InstallPackageByVersionAsync(packageName, versionRange, type, false, saveToPackageJson); + } + + public async Task InstallGlobalPackageByVersionAsync(string packageName, string versionRange) { + return await InstallPackageByVersionAsync(packageName, versionRange, DependencyType.Standard, true, false); + } + + private DependencyType GetDependencyType(string packageName) { + var type = DependencyType.Standard; + var root = _npmController.RootPackage; + if (null != root) { + var match = root.Modules[packageName]; + if (null != match) { + if (match.IsDevDependency) { + type = DependencyType.Development; + } else if (match.IsOptionalDependency) { + type = DependencyType.Optional; + } + } + } + return type; + } + + private async Task UninstallPackageAsync(string packageName, bool global) { + _command = new NpmUninstallCommand( + _npmController.FullPathToRootPackageDirectory, + packageName, + GetDependencyType(packageName), + global, + _npmController.PathToNpm); + return await DoCommandExecute(true); + } + + public async Task UninstallPackageAsync(string packageName) { + return await UninstallPackageAsync(packageName, false); + } + + public async Task UninstallGlobalPackageAsync(string packageName) { + return await UninstallPackageAsync(packageName, true); + } + + public async Task GetCatalogAsync(bool forceDownload, IProgress progress) { + _command = new NpmGetCatalogCommand( + _npmController.FullPathToRootPackageDirectory, + _npmController.CachePath, + forceDownload, + pathToNpm:_npmController.PathToNpm, + progress: progress); + await DoCommandExecute(false); + return (_command as NpmGetCatalogCommand).Catalog; + } + + public async Task UpdatePackagesAsync() { + return await UpdatePackagesAsync(new List()); + } + + private async Task UpdatePackagesAsync(IEnumerable packages, bool global) { + _command = new NpmUpdateCommand( + _npmController.FullPathToRootPackageDirectory, + packages, + global, + _npmController.PathToNpm); + return await DoCommandExecute(true); + } + + public async Task UpdatePackagesAsync(IEnumerable packages) { + return await UpdatePackagesAsync(packages, false); + } + + public async Task UpdateGlobalPackagesAsync(IEnumerable packages) { + return await UpdatePackagesAsync(packages, true); + } + + public async Task ExecuteNpmCommandAsync(string arguments) { + _command = new GenericNpmCommand( + _npmController.FullPathToRootPackageDirectory, + arguments, + _npmController.PathToNpm); + return await DoCommandExecute(true); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NpmController.cs b/Nodejs/Product/Npm/SPI/NpmController.cs index 0a96a7fe2..d94372c9d 100644 --- a/Nodejs/Product/Npm/SPI/NpmController.cs +++ b/Nodejs/Product/Npm/SPI/NpmController.cs @@ -1,367 +1,367 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NpmController : AbstractNpmLogSource, INpmController { - - private IPackageCatalog _sRepoCatalog; - private string _fullPathToRootPackageDirectory; - private string _cachePath; - private bool _showMissingDevOptionalSubPackages; - private INpmPathProvider _npmPathProvider; - private IRootPackage _rootPackage; - private IGlobalPackages _globalPackage; - private readonly object _lock = new object(); - - private readonly FileSystemWatcher _localWatcher; - private readonly FileSystemWatcher _globalWatcher; - - private Timer _fileSystemWatcherTimer; - private int _refreshRetryCount; - - private readonly object _fileBitsLock = new object(); - - - private bool _isDisposed; - private bool _isReloadingModules = false; - - public NpmController( - string fullPathToRootPackageDirectory, - string cachePath, - bool showMissingDevOptionalSubPackages = false, - INpmPathProvider npmPathProvider = null) { - _fullPathToRootPackageDirectory = fullPathToRootPackageDirectory; - _cachePath = cachePath; - _showMissingDevOptionalSubPackages = showMissingDevOptionalSubPackages; - _npmPathProvider = npmPathProvider; - - _localWatcher = CreateModuleDirectoryWatcherIfDirectoryExists(_fullPathToRootPackageDirectory); - _globalWatcher = CreateModuleDirectoryWatcherIfDirectoryExists(this.ListBaseDirectory); - - try { - ReloadModules(); - } catch (NpmNotFoundException) { } - } - - internal string FullPathToRootPackageDirectory { - get { return _fullPathToRootPackageDirectory; } - } - - internal string PathToNpm { - get { - try { - return null == _npmPathProvider ? null : _npmPathProvider.PathToNpm; - } catch (NpmNotFoundException) { - return null; - } - } - } - - internal string CachePath { - get { return _cachePath; } - } - - public event EventHandler StartingRefresh; - - private void Fire(EventHandler handlers) { - if (null != handlers) { - handlers(this, EventArgs.Empty); - } - } - - private void OnStartingRefresh() { - Fire(StartingRefresh); - } - - public event EventHandler FinishedRefresh; - - private void OnFinishedRefresh() { - Fire(FinishedRefresh); - } - - public void Refresh() { - RefreshAsync().ContinueWith(t => { - var ex = t.Exception; - if (ex != null) { - OnOutputLogged(ex.ToString()); -#if DEBUG - Debug.Fail(ex.ToString()); -#endif - } - }); - } - - public async Task RefreshAsync() { - OnStartingRefresh(); - try { - lock (_fileBitsLock) { - if (_isReloadingModules) { - RestartFileSystemWatcherTimer(); - return; - } else { - _isReloadingModules = true; - } - } - - RootPackage = RootPackageFactory.Create( - _fullPathToRootPackageDirectory, - _showMissingDevOptionalSubPackages); - - var command = new NpmBinCommand(_fullPathToRootPackageDirectory, true, PathToNpm); - - GlobalPackages = (await command.ExecuteAsync()) - ? RootPackageFactory.Create(command.BinDirectory) - : null; - } catch (IOException) { - // Can sometimes happen when packages are still installing because the file may still be used by another process - } finally { - lock (_fileBitsLock) { - _isReloadingModules = false; - } - if (RootPackage == null) { - OnOutputLogged("Error - Cannot load local packages."); - } - if (GlobalPackages == null) { - OnOutputLogged("Error - Cannot load global packages."); - } - OnFinishedRefresh(); - } - } - - public string ListBaseDirectory { - get { - var command = new NpmBinCommand(_fullPathToRootPackageDirectory, true, PathToNpm); - - // TODO - use GetAwaiter().GetResult() instead. - // https://nodejstools.codeplex.com/workitem/1849 - if (Task.Run(async () => { return await command.ExecuteAsync(); }).Result) { - return command.BinDirectory; - } - - return null; - } - } - - public IRootPackage RootPackage { - get { - lock (_lock) { - return _rootPackage; - } - } - - private set { - lock (_lock) { - _rootPackage = value; - } - } - } - - public IGlobalPackages GlobalPackages { - get { - lock (_lock) { - return _globalPackage; - } - } - private set { - lock (_lock) { - _globalPackage = value; - } - } - } - - public INpmCommander CreateNpmCommander() { - return new NpmCommander(this); - } - - public void LogCommandStarted(object sender, EventArgs args) { - OnCommandStarted(); - } - - public void LogOutput(object sender, NpmLogEventArgs e) { - OnOutputLogged(e.LogText); - } - - public void LogError(object sender, NpmLogEventArgs e) { - OnErrorLogged(e.LogText); - } - - public void LogException(object sender, NpmExceptionEventArgs e) { - OnExceptionLogged(e.Exception); - } - - public void LogCommandCompleted(object sender, NpmCommandCompletedEventArgs e) { - OnCommandCompleted(e.Arguments, e.WithErrors, e.Cancelled); - } - - public async Task GetRepositoryCatalogAsync(bool forceDownload, IProgress progress) { - // This should really be thread-safe but await can't be inside a lock so - // we'll just have to hope and pray this doesn't happen concurrently. Worst - // case is we'll end up with two retrievals, one of which will be binned, - // which isn't the end of the world. - _sRepoCatalog = null; - if (null == _sRepoCatalog || _sRepoCatalog.ResultsCount == 0 || forceDownload) { - Exception ex = null; - using (var commander = CreateNpmCommander()) { - EventHandler exHandler = (sender, args) => { LogException(sender, args); ex = args.Exception; }; - commander.ErrorLogged += LogError; - commander.ExceptionLogged += exHandler; - _sRepoCatalog = await commander.GetCatalogAsync(forceDownload, progress); - commander.ErrorLogged -= LogError; - commander.ExceptionLogged -= exHandler; - } - if (null != ex) { - OnOutputLogged(ex.ToString()); - throw ex; - } - } - return _sRepoCatalog; - } - - public IPackageCatalog MostRecentlyLoadedCatalog { - get { return _sRepoCatalog; } - } - - - private FileSystemWatcher CreateModuleDirectoryWatcherIfDirectoryExists(string directory) { - if (!Directory.Exists(directory)) { - return null; - } - - FileSystemWatcher watcher = null; - try { - watcher = new FileSystemWatcher(directory) { - NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime, - IncludeSubdirectories = true - }; - - watcher.Changed += Watcher_Modified; - watcher.Created += Watcher_Modified; - watcher.Deleted += Watcher_Modified; - watcher.EnableRaisingEvents = true; - } catch (Exception ex) { - if (watcher != null) { - watcher.Dispose(); - } - if (ex is IOException || ex is ArgumentException) { - Debug.WriteLine("Error starting FileSystemWatcher:\r\n{0}", ex); - } else { - throw; - } - } - - return watcher; - } - - private void Watcher_Modified(object sender, FileSystemEventArgs e) { - string path = e.FullPath; - - // Check that the file is either a package.json file, or exists in the node_modules directory - // This allows us to properly detect both installed and uninstalled/linked packages (where we don't receive an event for package.json) - if (path.EndsWith("package.json", StringComparison.OrdinalIgnoreCase) || path.IndexOf(NodejsConstants.NodeModulesFolder, StringComparison.OrdinalIgnoreCase) != -1) { - RestartFileSystemWatcherTimer(); - } - - return; - } - - - private void RestartFileSystemWatcherTimer() { - lock (_fileBitsLock) { - if (null != _fileSystemWatcherTimer) { - _fileSystemWatcherTimer.Dispose(); - } - - // Be sure to update the FileWatcher in NodejsProjectNode if the dueTime value changes. - _fileSystemWatcherTimer = new Timer(o => UpdateModulesFromTimer(), null, 1000, Timeout.Infinite); - } - } - - private void UpdateModulesFromTimer() { - lock (_fileBitsLock) { - if (null != _fileSystemWatcherTimer) { - _fileSystemWatcherTimer.Dispose(); - _fileSystemWatcherTimer = null; - } - } - - ReloadModules(); - } - - - private void ReloadModules() { - var retry = false; - Exception ex = null; - try { - this.Refresh(); - } catch (PackageJsonException pje) { - retry = true; - ex = pje; - } catch (AggregateException ae) { - retry = true; - ex = ae; - } catch (FileLoadException fle) { - // Fixes bug reported in work item 447 - just wait a bit and retry! - retry = true; - ex = fle; - } - - if (retry) { - if (_refreshRetryCount < 5) { - ++_refreshRetryCount; - RestartFileSystemWatcherTimer(); - } else { - OnExceptionLogged(ex); - } - } - } - - public void Dispose() { - if (!_isDisposed) { - lock (_fileBitsLock) { - if (_localWatcher != null) { - _localWatcher.Changed -= Watcher_Modified; - _localWatcher.Created -= Watcher_Modified; - _localWatcher.Deleted -= Watcher_Modified; - _localWatcher.Dispose(); - } - - if (_globalWatcher != null) { - _globalWatcher.Changed -= Watcher_Modified; - _globalWatcher.Created -= Watcher_Modified; - _globalWatcher.Deleted -= Watcher_Modified; - _globalWatcher.Dispose(); - } - } - - lock (_fileBitsLock) { - if (null != _fileSystemWatcherTimer) { - _fileSystemWatcherTimer.Dispose(); - _fileSystemWatcherTimer = null; - } - } - - _isDisposed = true; - } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NpmController : AbstractNpmLogSource, INpmController { + + private IPackageCatalog _sRepoCatalog; + private string _fullPathToRootPackageDirectory; + private string _cachePath; + private bool _showMissingDevOptionalSubPackages; + private INpmPathProvider _npmPathProvider; + private IRootPackage _rootPackage; + private IGlobalPackages _globalPackage; + private readonly object _lock = new object(); + + private readonly FileSystemWatcher _localWatcher; + private readonly FileSystemWatcher _globalWatcher; + + private Timer _fileSystemWatcherTimer; + private int _refreshRetryCount; + + private readonly object _fileBitsLock = new object(); + + + private bool _isDisposed; + private bool _isReloadingModules = false; + + public NpmController( + string fullPathToRootPackageDirectory, + string cachePath, + bool showMissingDevOptionalSubPackages = false, + INpmPathProvider npmPathProvider = null) { + _fullPathToRootPackageDirectory = fullPathToRootPackageDirectory; + _cachePath = cachePath; + _showMissingDevOptionalSubPackages = showMissingDevOptionalSubPackages; + _npmPathProvider = npmPathProvider; + + _localWatcher = CreateModuleDirectoryWatcherIfDirectoryExists(_fullPathToRootPackageDirectory); + _globalWatcher = CreateModuleDirectoryWatcherIfDirectoryExists(this.ListBaseDirectory); + + try { + ReloadModules(); + } catch (NpmNotFoundException) { } + } + + internal string FullPathToRootPackageDirectory { + get { return _fullPathToRootPackageDirectory; } + } + + internal string PathToNpm { + get { + try { + return null == _npmPathProvider ? null : _npmPathProvider.PathToNpm; + } catch (NpmNotFoundException) { + return null; + } + } + } + + internal string CachePath { + get { return _cachePath; } + } + + public event EventHandler StartingRefresh; + + private void Fire(EventHandler handlers) { + if (null != handlers) { + handlers(this, EventArgs.Empty); + } + } + + private void OnStartingRefresh() { + Fire(StartingRefresh); + } + + public event EventHandler FinishedRefresh; + + private void OnFinishedRefresh() { + Fire(FinishedRefresh); + } + + public void Refresh() { + RefreshAsync().ContinueWith(t => { + var ex = t.Exception; + if (ex != null) { + OnOutputLogged(ex.ToString()); +#if DEBUG + Debug.Fail(ex.ToString()); +#endif + } + }); + } + + public async Task RefreshAsync() { + OnStartingRefresh(); + try { + lock (_fileBitsLock) { + if (_isReloadingModules) { + RestartFileSystemWatcherTimer(); + return; + } else { + _isReloadingModules = true; + } + } + + RootPackage = RootPackageFactory.Create( + _fullPathToRootPackageDirectory, + _showMissingDevOptionalSubPackages); + + var command = new NpmBinCommand(_fullPathToRootPackageDirectory, true, PathToNpm); + + GlobalPackages = (await command.ExecuteAsync()) + ? RootPackageFactory.Create(command.BinDirectory) + : null; + } catch (IOException) { + // Can sometimes happen when packages are still installing because the file may still be used by another process + } finally { + lock (_fileBitsLock) { + _isReloadingModules = false; + } + if (RootPackage == null) { + OnOutputLogged("Error - Cannot load local packages."); + } + if (GlobalPackages == null) { + OnOutputLogged("Error - Cannot load global packages."); + } + OnFinishedRefresh(); + } + } + + public string ListBaseDirectory { + get { + var command = new NpmBinCommand(_fullPathToRootPackageDirectory, true, PathToNpm); + + // TODO - use GetAwaiter().GetResult() instead. + // https://nodejstools.codeplex.com/workitem/1849 + if (Task.Run(async () => { return await command.ExecuteAsync(); }).Result) { + return command.BinDirectory; + } + + return null; + } + } + + public IRootPackage RootPackage { + get { + lock (_lock) { + return _rootPackage; + } + } + + private set { + lock (_lock) { + _rootPackage = value; + } + } + } + + public IGlobalPackages GlobalPackages { + get { + lock (_lock) { + return _globalPackage; + } + } + private set { + lock (_lock) { + _globalPackage = value; + } + } + } + + public INpmCommander CreateNpmCommander() { + return new NpmCommander(this); + } + + public void LogCommandStarted(object sender, EventArgs args) { + OnCommandStarted(); + } + + public void LogOutput(object sender, NpmLogEventArgs e) { + OnOutputLogged(e.LogText); + } + + public void LogError(object sender, NpmLogEventArgs e) { + OnErrorLogged(e.LogText); + } + + public void LogException(object sender, NpmExceptionEventArgs e) { + OnExceptionLogged(e.Exception); + } + + public void LogCommandCompleted(object sender, NpmCommandCompletedEventArgs e) { + OnCommandCompleted(e.Arguments, e.WithErrors, e.Cancelled); + } + + public async Task GetRepositoryCatalogAsync(bool forceDownload, IProgress progress) { + // This should really be thread-safe but await can't be inside a lock so + // we'll just have to hope and pray this doesn't happen concurrently. Worst + // case is we'll end up with two retrievals, one of which will be binned, + // which isn't the end of the world. + _sRepoCatalog = null; + if (null == _sRepoCatalog || _sRepoCatalog.ResultsCount == 0 || forceDownload) { + Exception ex = null; + using (var commander = CreateNpmCommander()) { + EventHandler exHandler = (sender, args) => { LogException(sender, args); ex = args.Exception; }; + commander.ErrorLogged += LogError; + commander.ExceptionLogged += exHandler; + _sRepoCatalog = await commander.GetCatalogAsync(forceDownload, progress); + commander.ErrorLogged -= LogError; + commander.ExceptionLogged -= exHandler; + } + if (null != ex) { + OnOutputLogged(ex.ToString()); + throw ex; + } + } + return _sRepoCatalog; + } + + public IPackageCatalog MostRecentlyLoadedCatalog { + get { return _sRepoCatalog; } + } + + + private FileSystemWatcher CreateModuleDirectoryWatcherIfDirectoryExists(string directory) { + if (!Directory.Exists(directory)) { + return null; + } + + FileSystemWatcher watcher = null; + try { + watcher = new FileSystemWatcher(directory) { + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime, + IncludeSubdirectories = true + }; + + watcher.Changed += Watcher_Modified; + watcher.Created += Watcher_Modified; + watcher.Deleted += Watcher_Modified; + watcher.EnableRaisingEvents = true; + } catch (Exception ex) { + if (watcher != null) { + watcher.Dispose(); + } + if (ex is IOException || ex is ArgumentException) { + Debug.WriteLine("Error starting FileSystemWatcher:\r\n{0}", ex); + } else { + throw; + } + } + + return watcher; + } + + private void Watcher_Modified(object sender, FileSystemEventArgs e) { + string path = e.FullPath; + + // Check that the file is either a package.json file, or exists in the node_modules directory + // This allows us to properly detect both installed and uninstalled/linked packages (where we don't receive an event for package.json) + if (path.EndsWith("package.json", StringComparison.OrdinalIgnoreCase) || path.IndexOf(NodejsConstants.NodeModulesFolder, StringComparison.OrdinalIgnoreCase) != -1) { + RestartFileSystemWatcherTimer(); + } + + return; + } + + + private void RestartFileSystemWatcherTimer() { + lock (_fileBitsLock) { + if (null != _fileSystemWatcherTimer) { + _fileSystemWatcherTimer.Dispose(); + } + + // Be sure to update the FileWatcher in NodejsProjectNode if the dueTime value changes. + _fileSystemWatcherTimer = new Timer(o => UpdateModulesFromTimer(), null, 1000, Timeout.Infinite); + } + } + + private void UpdateModulesFromTimer() { + lock (_fileBitsLock) { + if (null != _fileSystemWatcherTimer) { + _fileSystemWatcherTimer.Dispose(); + _fileSystemWatcherTimer = null; + } + } + + ReloadModules(); + } + + + private void ReloadModules() { + var retry = false; + Exception ex = null; + try { + this.Refresh(); + } catch (PackageJsonException pje) { + retry = true; + ex = pje; + } catch (AggregateException ae) { + retry = true; + ex = ae; + } catch (FileLoadException fle) { + // Fixes bug reported in work item 447 - just wait a bit and retry! + retry = true; + ex = fle; + } + + if (retry) { + if (_refreshRetryCount < 5) { + ++_refreshRetryCount; + RestartFileSystemWatcherTimer(); + } else { + OnExceptionLogged(ex); + } + } + } + + public void Dispose() { + if (!_isDisposed) { + lock (_fileBitsLock) { + if (_localWatcher != null) { + _localWatcher.Changed -= Watcher_Modified; + _localWatcher.Created -= Watcher_Modified; + _localWatcher.Deleted -= Watcher_Modified; + _localWatcher.Dispose(); + } + + if (_globalWatcher != null) { + _globalWatcher.Changed -= Watcher_Modified; + _globalWatcher.Created -= Watcher_Modified; + _globalWatcher.Deleted -= Watcher_Modified; + _globalWatcher.Dispose(); + } + } + + lock (_fileBitsLock) { + if (null != _fileSystemWatcherTimer) { + _fileSystemWatcherTimer.Dispose(); + _fileSystemWatcherTimer = null; + } + } + + _isDisposed = true; + } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NpmGetCatalogCommand.cs b/Nodejs/Product/Npm/SPI/NpmGetCatalogCommand.cs index 57e269b55..5e97c57af 100644 --- a/Nodejs/Product/Npm/SPI/NpmGetCatalogCommand.cs +++ b/Nodejs/Product/Npm/SPI/NpmGetCatalogCommand.cs @@ -68,206 +68,206 @@ internal void ParseResultsAndAddToDatabase(TextReader reader, db.RunInTransaction(() => { db.CreateRegistryTableIfNotExists(); - using (var jsonReader = new JsonTextReader(reader)) { - /* - The schema seems to have changed over time. - - The first format we need to handle is an object literal. It - starts with an "_updated" property, with a value of the - timestamp it was retrived, and then a property for each - package, with a name of the package name, and a value which - is on object literal representing the package info. An example - downloaded may start: - -{ -"_updated": 1413573404788, -"unlink-empty-files": { - "name": "unlink-empty-files", - "description": "given a directory, unlink (remove) all files with a length of 0", - "dist-tags": { "latest": "1.0.1" }, - "maintainers": [ - { - "name": "kesla", -etc. - - The other format is an array literal, where each element is an - object literal for a package, similar to the value of the - properties above, for example: - -[ -{"name":"008-somepackage","description":"Test Package","dist-tags":{"latest":"1.1.1"}.. -, -{"name":"01-simple","description":"That is the first app in order to study the ..." -, -etc. - - In this second format, there is no "_updated" property with a - timestamp, and the 'Date' timestamp from the HTTP request for - the data is used instead. - - The NPM code that handles the payload seems to be written in - a way to handle both formats - See https://github.com/npm/npm/blob/2.x-release/lib/cache/update-index.js#L87 - */ - jsonReader.Read(); - switch (jsonReader.TokenType) { - case JsonToken.StartObject: - ReadPackagesFromObject(db, jsonReader, registryUrl); - break; - case JsonToken.StartArray: - // The array format doesn't contain the "_update" field, - // so create a rough timestamp. Use the time from 30 mins - // ago (to set it before the download request started), - // converted to a JavaScript value (milliseconds since - // start of 1970) - var timestamp = DateTime.UtcNow - .Subtract(new DateTime(1970, 1, 1, 0, 30, 0, DateTimeKind.Utc)) - .TotalMilliseconds; - ReadPackagesFromArray(db, jsonReader); - db.InsertOrReplace(new RegistryInfo() { - RegistryUrl = registryUrl, - Revision = (long)timestamp, - UpdatedOn = DateTime.Now - }); - break; - default: - throw new JsonException("Unexpected JSON token at start of NPM catalog data"); - } + using (var jsonReader = new JsonTextReader(reader)) { + /* + The schema seems to have changed over time. + + The first format we need to handle is an object literal. It + starts with an "_updated" property, with a value of the + timestamp it was retrived, and then a property for each + package, with a name of the package name, and a value which + is on object literal representing the package info. An example + downloaded may start: + +{ +"_updated": 1413573404788, +"unlink-empty-files": { + "name": "unlink-empty-files", + "description": "given a directory, unlink (remove) all files with a length of 0", + "dist-tags": { "latest": "1.0.1" }, + "maintainers": [ + { + "name": "kesla", +etc. + + The other format is an array literal, where each element is an + object literal for a package, similar to the value of the + properties above, for example: + +[ +{"name":"008-somepackage","description":"Test Package","dist-tags":{"latest":"1.1.1"}.. +, +{"name":"01-simple","description":"That is the first app in order to study the ..." +, +etc. + + In this second format, there is no "_updated" property with a + timestamp, and the 'Date' timestamp from the HTTP request for + the data is used instead. + + The NPM code that handles the payload seems to be written in + a way to handle both formats + See https://github.com/npm/npm/blob/2.x-release/lib/cache/update-index.js#L87 + */ + jsonReader.Read(); + switch (jsonReader.TokenType) { + case JsonToken.StartObject: + ReadPackagesFromObject(db, jsonReader, registryUrl); + break; + case JsonToken.StartArray: + // The array format doesn't contain the "_update" field, + // so create a rough timestamp. Use the time from 30 mins + // ago (to set it before the download request started), + // converted to a JavaScript value (milliseconds since + // start of 1970) + var timestamp = DateTime.UtcNow + .Subtract(new DateTime(1970, 1, 1, 0, 30, 0, DateTimeKind.Utc)) + .TotalMilliseconds; + ReadPackagesFromArray(db, jsonReader); + db.InsertOrReplace(new RegistryInfo() { + RegistryUrl = registryUrl, + Revision = (long)timestamp, + UpdatedOn = DateTime.Now + }); + break; + default: + throw new JsonException("Unexpected JSON token at start of NPM catalog data"); + } } // FTS doesn't support INSERT OR REPLACE. This is the most efficient way to bypass that limitation. db.Execute("DELETE FROM CatalogEntry WHERE docid NOT IN (SELECT MAX(docid) FROM CatalogEntry GROUP BY Name)"); }); } - } - - private void ReadPackagesFromObject(SQLiteConnection db, - JsonTextReader jsonReader, - string registryUrl) { - var builder = new NodeModuleBuilder(); - while (jsonReader.Read()) { - if (jsonReader.TokenType == JsonToken.EndObject) { - // Reached the end of the object literal containing the data. - // This should be the normal exit point. - return; - } - - // Every property should be either the "_updated" value, or a package - if (jsonReader.TokenType != JsonToken.PropertyName) { - throw new JsonException("Unexpected JSON token in NPM catalog data"); - } - string propertyName = (string)jsonReader.Value; - - // If it's "_updated", update the revision info. - if (propertyName.Equals("_updated", StringComparison.Ordinal)) { - jsonReader.Read(); - db.InsertOrReplace(new RegistryInfo() { - RegistryUrl = registryUrl, - Revision = (long)jsonReader.Value, - UpdatedOn = DateTime.Now - }); - continue; - } - - // Else the property should be an object literal representing the package - jsonReader.Read(); - if (jsonReader.TokenType == JsonToken.StartObject) { - IPackage package = ReadPackage(jsonReader, builder); - if (package != null) { - InsertCatalogEntry(db, package); - } - } else { - throw new JsonException("Unexpected JSON token reading a package from the NPM catalog data"); - } - } - throw new JsonException("Unexpected end of stream reading the NPM catalog data object"); - } - - private void ReadPackagesFromArray(SQLiteConnection db, JsonTextReader jsonReader) { - // Inside the array, each object is an NPM package - var builder = new NodeModuleBuilder(); - while (jsonReader.Read()) { - switch (jsonReader.TokenType) { - case JsonToken.StartObject: - IPackage package = ReadPackage(jsonReader, builder); - if (package != null) { - InsertCatalogEntry(db, package); - } - break; - case JsonToken.EndArray: - // This is the spot the function should always exit on valid data - return; - default: - throw new JsonException("Unexpected JSON token in NPM catalog data array"); - } - } - throw new JsonException("Unexpected end of stream reading the NPM catalog data array"); - } - - private IPackage ReadPackage(JsonTextReader jsonReader, NodeModuleBuilder builder) { - IPackage package = null; - builder.Reset(); - - try { - // The JsonTextReader should be positioned at the start of the - // object literal token for the package - var module = JToken.ReadFrom(jsonReader); - - builder.Name = (string)module["name"]; - if (string.IsNullOrEmpty(builder.Name)) { - // I don't believe this should ever happen if the data returned is - // well formed. Could throw an exception, but just skip instead for - // resiliency on the NTVS side. - return null; - } - - builder.AppendToDescription((string)module["description"] ?? string.Empty); - - var time = module["time"]; - if (time != null) { - builder.AppendToDate((string)time["modified"]); - } - - var distTags = module["dist-tags"]; - if (distTags != null) { - var latestVersion = distTags - .OfType() - .Where(v => (string)v.Name == "latest") - .Select(v => (string)v.Value) - .FirstOrDefault(); - - if (!string.IsNullOrEmpty(latestVersion)) { - try { - builder.LatestVersion = SemverVersion.Parse(latestVersion); - } catch (SemverVersionFormatException) { - OnOutputLogged(String.Format( - Resources.InvalidPackageSemVersion, - latestVersion, - builder.Name)); - } - } - } - - var versions = module["versions"]; - if (versions != null) { - builder.AvailableVersions = GetVersions(versions); - } - - AddKeywords(builder, module["keywords"]); - AddAuthor(builder, module["author"]); - AddHomepage(builder, module["homepage"]); - - package = builder.Build(); - } catch (InvalidOperationException) { - // Occurs if a JValue appears where we expect JProperty - return null; - } catch (ArgumentException) { - OnOutputLogged(string.Format(Resources.ParsingError, builder.Name)); - return null; - } - return package; - } - + } + + private void ReadPackagesFromObject(SQLiteConnection db, + JsonTextReader jsonReader, + string registryUrl) { + var builder = new NodeModuleBuilder(); + while (jsonReader.Read()) { + if (jsonReader.TokenType == JsonToken.EndObject) { + // Reached the end of the object literal containing the data. + // This should be the normal exit point. + return; + } + + // Every property should be either the "_updated" value, or a package + if (jsonReader.TokenType != JsonToken.PropertyName) { + throw new JsonException("Unexpected JSON token in NPM catalog data"); + } + string propertyName = (string)jsonReader.Value; + + // If it's "_updated", update the revision info. + if (propertyName.Equals("_updated", StringComparison.Ordinal)) { + jsonReader.Read(); + db.InsertOrReplace(new RegistryInfo() { + RegistryUrl = registryUrl, + Revision = (long)jsonReader.Value, + UpdatedOn = DateTime.Now + }); + continue; + } + + // Else the property should be an object literal representing the package + jsonReader.Read(); + if (jsonReader.TokenType == JsonToken.StartObject) { + IPackage package = ReadPackage(jsonReader, builder); + if (package != null) { + InsertCatalogEntry(db, package); + } + } else { + throw new JsonException("Unexpected JSON token reading a package from the NPM catalog data"); + } + } + throw new JsonException("Unexpected end of stream reading the NPM catalog data object"); + } + + private void ReadPackagesFromArray(SQLiteConnection db, JsonTextReader jsonReader) { + // Inside the array, each object is an NPM package + var builder = new NodeModuleBuilder(); + while (jsonReader.Read()) { + switch (jsonReader.TokenType) { + case JsonToken.StartObject: + IPackage package = ReadPackage(jsonReader, builder); + if (package != null) { + InsertCatalogEntry(db, package); + } + break; + case JsonToken.EndArray: + // This is the spot the function should always exit on valid data + return; + default: + throw new JsonException("Unexpected JSON token in NPM catalog data array"); + } + } + throw new JsonException("Unexpected end of stream reading the NPM catalog data array"); + } + + private IPackage ReadPackage(JsonTextReader jsonReader, NodeModuleBuilder builder) { + IPackage package = null; + builder.Reset(); + + try { + // The JsonTextReader should be positioned at the start of the + // object literal token for the package + var module = JToken.ReadFrom(jsonReader); + + builder.Name = (string)module["name"]; + if (string.IsNullOrEmpty(builder.Name)) { + // I don't believe this should ever happen if the data returned is + // well formed. Could throw an exception, but just skip instead for + // resiliency on the NTVS side. + return null; + } + + builder.AppendToDescription((string)module["description"] ?? string.Empty); + + var time = module["time"]; + if (time != null) { + builder.AppendToDate((string)time["modified"]); + } + + var distTags = module["dist-tags"]; + if (distTags != null) { + var latestVersion = distTags + .OfType() + .Where(v => (string)v.Name == "latest") + .Select(v => (string)v.Value) + .FirstOrDefault(); + + if (!string.IsNullOrEmpty(latestVersion)) { + try { + builder.LatestVersion = SemverVersion.Parse(latestVersion); + } catch (SemverVersionFormatException) { + OnOutputLogged(String.Format( + Resources.InvalidPackageSemVersion, + latestVersion, + builder.Name)); + } + } + } + + var versions = module["versions"]; + if (versions != null) { + builder.AvailableVersions = GetVersions(versions); + } + + AddKeywords(builder, module["keywords"]); + AddAuthor(builder, module["author"]); + AddHomepage(builder, module["homepage"]); + + package = builder.Build(); + } catch (InvalidOperationException) { + // Occurs if a JValue appears where we expect JProperty + return null; + } catch (ArgumentException) { + OnOutputLogged(string.Format(Resources.ParsingError, builder.Name)); + return null; + } + return package; + } + private static void InsertCatalogEntry(SQLiteConnection db, IPackage package) { db.Insert(new CatalogEntry() { Name = package.Name, diff --git a/Nodejs/Product/Npm/SPI/NpmInstallCommand.cs b/Nodejs/Product/Npm/SPI/NpmInstallCommand.cs index 03a9d7b4d..ce255750b 100644 --- a/Nodejs/Product/Npm/SPI/NpmInstallCommand.cs +++ b/Nodejs/Product/Npm/SPI/NpmInstallCommand.cs @@ -1,45 +1,45 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NpmInstallCommand : NpmCommand { - public NpmInstallCommand( - string fullPathToRootPackageDirectory, - string pathToNpm = null, - bool useFallbackIfNpmNotFound = true) - : base(fullPathToRootPackageDirectory, pathToNpm) { - Arguments = "install"; - } - - public NpmInstallCommand( - string fullPathToRootPackageDirectory, - string packageName, - string versionRange, - DependencyType type, - bool global = false, - bool saveToPackageJson = true, - string pathToNpm = null, - bool useFallbackIfNpmNotFound = true) - : base(fullPathToRootPackageDirectory, pathToNpm) { - Arguments = NpmArgumentBuilder.GetNpmInstallArguments( - packageName, - versionRange, - type, - global, - saveToPackageJson); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NpmInstallCommand : NpmCommand { + public NpmInstallCommand( + string fullPathToRootPackageDirectory, + string pathToNpm = null, + bool useFallbackIfNpmNotFound = true) + : base(fullPathToRootPackageDirectory, pathToNpm) { + Arguments = "install"; + } + + public NpmInstallCommand( + string fullPathToRootPackageDirectory, + string packageName, + string versionRange, + DependencyType type, + bool global = false, + bool saveToPackageJson = true, + string pathToNpm = null, + bool useFallbackIfNpmNotFound = true) + : base(fullPathToRootPackageDirectory, pathToNpm) { + Arguments = NpmArgumentBuilder.GetNpmInstallArguments( + packageName, + versionRange, + type, + global, + saveToPackageJson); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NpmSearchFilterStringComparer.cs b/Nodejs/Product/Npm/SPI/NpmSearchFilterStringComparer.cs index 9be2a79db..9d1745f4e 100644 --- a/Nodejs/Product/Npm/SPI/NpmSearchFilterStringComparer.cs +++ b/Nodejs/Product/Npm/SPI/NpmSearchFilterStringComparer.cs @@ -1,134 +1,134 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NpmSearchFilterStringComparer : AbstractNpmSearchComparer { - private readonly string _filterString; - - public NpmSearchFilterStringComparer(string filterString) { - _filterString = filterString; - } - - private int GetExactKeywordMatchCount(IPackage source) { - return source.Keywords == null ? 0 : source.Keywords.Count(keyword => keyword.ToLower() == _filterString); - } - - private int GetStartsWithMatchCount(IPackage source) { - return source.Keywords == null ? 0 : source.Keywords.Count(keyword => keyword.ToLower().StartsWith(_filterString)); - } - - private int GetPartialKeywordMatchCount(IPackage source) { - return source.Keywords == null ? 0 : source.Keywords.Count(keyword => keyword.ToLower().Contains(_filterString)); - } - - private new int CompareBasedOnKeywords(IPackage x, IPackage y) { - int xCount = GetExactKeywordMatchCount(x), - yCount = GetExactKeywordMatchCount(y); - - if (xCount == yCount) { - xCount = GetStartsWithMatchCount(x); - yCount = GetStartsWithMatchCount(y); - - if (xCount == yCount) { - xCount = GetPartialKeywordMatchCount(x); - yCount = GetPartialKeywordMatchCount(y); - - if (xCount == yCount) { - var result = base.CompareBasedOnKeywords(x, y); - return 0 == result - ? string.Compare(x.Name, y.Name, StringComparison.CurrentCulture) - : result; - } - } - } - - return xCount > yCount ? -1 : 1; - } - - private int CompareBasedOnDescriptions(IPackage x, IPackage y) { - string d1 = x.Description, d2 = y.Description; - if (null == d1) { - if (null == d2) { - return CompareBasedOnKeywords(x, y); - } - - return d2.Contains(_filterString) ? 1 : CompareBasedOnKeywords(x, y); - } - - if (null == d2) { - return d1.Contains(_filterString) ? -1 : CompareBasedOnKeywords(x, y); - } - - if (d1.Contains(_filterString)) { - return d2.Contains(_filterString) - ? string.Compare(d1, d2, StringComparison.CurrentCulture) - : -1; - } - - if (d2.Contains(_filterString)) { - return 1; - } - - return CompareBasedOnKeywords(x, y); - } - - public override int Compare(IPackage x, IPackage y) { - if (string.IsNullOrEmpty(_filterString)) { - // Version numbers are irrelevant, and names are good enough since they must be unique in the repo - return string.Compare(x.Name, y.Name, StringComparison.CurrentCulture); - } - - if (x.Name == _filterString) { - return y.Name == _filterString - ? 0 // In theory should never happen since package names have to be unique - : -1; // Exact matches are always the best - } - - if (y.Name == _filterString) { // Again, exact matches are always best - return 1; - } - - // Matches at the beginning are better than matches in the string - if (x.Name.StartsWith(_filterString)) { - if (y.Name.StartsWith(_filterString)) { - var result = string.Compare(x.Name, y.Name, StringComparison.CurrentCulture); - return 0 == result ? CompareBasedOnDescriptions(x, y) : result; - } - - return -1; - } - - if (y.Name.StartsWith(_filterString)) { - return 1; - } - - if (x.Name.Contains(_filterString)) { - return y.Name.Contains(_filterString) - ? string.Compare(x.Name, y.Name, StringComparison.CurrentCulture) - : -1; - } - - if (y.Name.Contains(_filterString)) { - return 1; - } - - return CompareBasedOnDescriptions(x, y); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NpmSearchFilterStringComparer : AbstractNpmSearchComparer { + private readonly string _filterString; + + public NpmSearchFilterStringComparer(string filterString) { + _filterString = filterString; + } + + private int GetExactKeywordMatchCount(IPackage source) { + return source.Keywords == null ? 0 : source.Keywords.Count(keyword => keyword.ToLower() == _filterString); + } + + private int GetStartsWithMatchCount(IPackage source) { + return source.Keywords == null ? 0 : source.Keywords.Count(keyword => keyword.ToLower().StartsWith(_filterString)); + } + + private int GetPartialKeywordMatchCount(IPackage source) { + return source.Keywords == null ? 0 : source.Keywords.Count(keyword => keyword.ToLower().Contains(_filterString)); + } + + private new int CompareBasedOnKeywords(IPackage x, IPackage y) { + int xCount = GetExactKeywordMatchCount(x), + yCount = GetExactKeywordMatchCount(y); + + if (xCount == yCount) { + xCount = GetStartsWithMatchCount(x); + yCount = GetStartsWithMatchCount(y); + + if (xCount == yCount) { + xCount = GetPartialKeywordMatchCount(x); + yCount = GetPartialKeywordMatchCount(y); + + if (xCount == yCount) { + var result = base.CompareBasedOnKeywords(x, y); + return 0 == result + ? string.Compare(x.Name, y.Name, StringComparison.CurrentCulture) + : result; + } + } + } + + return xCount > yCount ? -1 : 1; + } + + private int CompareBasedOnDescriptions(IPackage x, IPackage y) { + string d1 = x.Description, d2 = y.Description; + if (null == d1) { + if (null == d2) { + return CompareBasedOnKeywords(x, y); + } + + return d2.Contains(_filterString) ? 1 : CompareBasedOnKeywords(x, y); + } + + if (null == d2) { + return d1.Contains(_filterString) ? -1 : CompareBasedOnKeywords(x, y); + } + + if (d1.Contains(_filterString)) { + return d2.Contains(_filterString) + ? string.Compare(d1, d2, StringComparison.CurrentCulture) + : -1; + } + + if (d2.Contains(_filterString)) { + return 1; + } + + return CompareBasedOnKeywords(x, y); + } + + public override int Compare(IPackage x, IPackage y) { + if (string.IsNullOrEmpty(_filterString)) { + // Version numbers are irrelevant, and names are good enough since they must be unique in the repo + return string.Compare(x.Name, y.Name, StringComparison.CurrentCulture); + } + + if (x.Name == _filterString) { + return y.Name == _filterString + ? 0 // In theory should never happen since package names have to be unique + : -1; // Exact matches are always the best + } + + if (y.Name == _filterString) { // Again, exact matches are always best + return 1; + } + + // Matches at the beginning are better than matches in the string + if (x.Name.StartsWith(_filterString)) { + if (y.Name.StartsWith(_filterString)) { + var result = string.Compare(x.Name, y.Name, StringComparison.CurrentCulture); + return 0 == result ? CompareBasedOnDescriptions(x, y) : result; + } + + return -1; + } + + if (y.Name.StartsWith(_filterString)) { + return 1; + } + + if (x.Name.Contains(_filterString)) { + return y.Name.Contains(_filterString) + ? string.Compare(x.Name, y.Name, StringComparison.CurrentCulture) + : -1; + } + + if (y.Name.Contains(_filterString)) { + return 1; + } + + return CompareBasedOnDescriptions(x, y); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NpmSearchRegexComparer.cs b/Nodejs/Product/Npm/SPI/NpmSearchRegexComparer.cs index f2055bd3a..547ee6e4d 100644 --- a/Nodejs/Product/Npm/SPI/NpmSearchRegexComparer.cs +++ b/Nodejs/Product/Npm/SPI/NpmSearchRegexComparer.cs @@ -1,100 +1,100 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NpmSearchRegexComparer : AbstractNpmSearchComparer { - - private readonly Regex _regex; - - public NpmSearchRegexComparer(Regex regex) { - _regex = regex; - } - - private int GetFirstMatchIndex(MatchCollection matches) { - return null == matches || matches.Count == 0 ? int.MaxValue : matches[0].Index; - } - - private IList GetKeywordMatches(IPackage x) { - return x.Keywords.Select(keyword => _regex.Matches(keyword)).Where(matches => matches.Count > 0).ToList(); - } - - private int GetSumOfLowestMatchIndexes(IList matches) { - return matches.Count == 0 ? int.MaxValue : matches.Sum(collection => collection[0].Index); - } - - private new int CompareBasedOnKeywords(IPackage x, IPackage y) { - var xMatches = GetKeywordMatches(x); - var yMatches = GetKeywordMatches(y); - - var result = -xMatches.Count.CompareTo(yMatches.Count); // Highest number wins - if (0 == result) { - result = GetSumOfLowestMatchIndexes(xMatches).CompareTo(GetSumOfLowestMatchIndexes(yMatches)); - if (0 == result) { - result = base.CompareBasedOnKeywords(x, y); - } - } - - return result; - } - - private int CompareBasedOnDescriptions(IPackage x, IPackage y) { - string d1 = x.Description, d2 = y.Description; - if (null == d1 || null == d2) { - return CompareBasedOnKeywords(x, y); - } - - var xMatches = _regex.Matches(d1); - var yMatches = _regex.Matches(d2); - - var result = GetFirstMatchIndex(xMatches).CompareTo(GetFirstMatchIndex(yMatches)); - if (0 == result) { - result = -xMatches.Count.CompareTo(yMatches.Count); - if (0 == result) { - result = CompareBasedOnKeywords(x, y); - if (0 == result) { - result = string.Compare(d1, d2, StringComparison.CurrentCulture); - } - } - } - - return result; - } - - public override int Compare(IPackage x, IPackage y) { - var xMatches = _regex.Matches(x.Name); - var yMatches = _regex.Matches(y.Name); - - var result = GetFirstMatchIndex(xMatches).CompareTo(GetFirstMatchIndex(yMatches)); - if (0 == result) { - result = -xMatches.Count.CompareTo(yMatches.Count); // Highest number wins - if (0 == result) { - result = CompareBasedOnDescriptions(x, y); - if (0 == result) { - result = string.Compare(x.Name, y.Name, StringComparison.CurrentCulture); - } - } - } - - return result; - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NpmSearchRegexComparer : AbstractNpmSearchComparer { + + private readonly Regex _regex; + + public NpmSearchRegexComparer(Regex regex) { + _regex = regex; + } + + private int GetFirstMatchIndex(MatchCollection matches) { + return null == matches || matches.Count == 0 ? int.MaxValue : matches[0].Index; + } + + private IList GetKeywordMatches(IPackage x) { + return x.Keywords.Select(keyword => _regex.Matches(keyword)).Where(matches => matches.Count > 0).ToList(); + } + + private int GetSumOfLowestMatchIndexes(IList matches) { + return matches.Count == 0 ? int.MaxValue : matches.Sum(collection => collection[0].Index); + } + + private new int CompareBasedOnKeywords(IPackage x, IPackage y) { + var xMatches = GetKeywordMatches(x); + var yMatches = GetKeywordMatches(y); + + var result = -xMatches.Count.CompareTo(yMatches.Count); // Highest number wins + if (0 == result) { + result = GetSumOfLowestMatchIndexes(xMatches).CompareTo(GetSumOfLowestMatchIndexes(yMatches)); + if (0 == result) { + result = base.CompareBasedOnKeywords(x, y); + } + } + + return result; + } + + private int CompareBasedOnDescriptions(IPackage x, IPackage y) { + string d1 = x.Description, d2 = y.Description; + if (null == d1 || null == d2) { + return CompareBasedOnKeywords(x, y); + } + + var xMatches = _regex.Matches(d1); + var yMatches = _regex.Matches(d2); + + var result = GetFirstMatchIndex(xMatches).CompareTo(GetFirstMatchIndex(yMatches)); + if (0 == result) { + result = -xMatches.Count.CompareTo(yMatches.Count); + if (0 == result) { + result = CompareBasedOnKeywords(x, y); + if (0 == result) { + result = string.Compare(d1, d2, StringComparison.CurrentCulture); + } + } + } + + return result; + } + + public override int Compare(IPackage x, IPackage y) { + var xMatches = _regex.Matches(x.Name); + var yMatches = _regex.Matches(y.Name); + + var result = GetFirstMatchIndex(xMatches).CompareTo(GetFirstMatchIndex(yMatches)); + if (0 == result) { + result = -xMatches.Count.CompareTo(yMatches.Count); // Highest number wins + if (0 == result) { + result = CompareBasedOnDescriptions(x, y); + if (0 == result) { + result = string.Compare(x.Name, y.Name, StringComparison.CurrentCulture); + } + } + } + + return result; + } + } +} diff --git a/Nodejs/Product/Npm/SPI/NpmUninstallCommand.cs b/Nodejs/Product/Npm/SPI/NpmUninstallCommand.cs index 95c414240..f92640afd 100644 --- a/Nodejs/Product/Npm/SPI/NpmUninstallCommand.cs +++ b/Nodejs/Product/Npm/SPI/NpmUninstallCommand.cs @@ -1,37 +1,37 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NpmUninstallCommand : NpmCommand { - public NpmUninstallCommand( - string fullPathToRootPackageDirectory, - string packageName, - DependencyType type, - bool global = false, - string pathToNpm = null, - bool useFallbackIfNpmNotFound = true) - : base(fullPathToRootPackageDirectory, pathToNpm) { - Arguments = global - ? string.Format("uninstall {0} --g", packageName) - : string.Format( - "uninstall {0} --{1}", - packageName, - (type == DependencyType.Standard - ? "save" - : (type == DependencyType.Development ? "save-dev" : "save-optional"))); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NpmUninstallCommand : NpmCommand { + public NpmUninstallCommand( + string fullPathToRootPackageDirectory, + string packageName, + DependencyType type, + bool global = false, + string pathToNpm = null, + bool useFallbackIfNpmNotFound = true) + : base(fullPathToRootPackageDirectory, pathToNpm) { + Arguments = global + ? string.Format("uninstall {0} --g", packageName) + : string.Format( + "uninstall {0} --{1}", + packageName, + (type == DependencyType.Standard + ? "save" + : (type == DependencyType.Development ? "save-dev" : "save-optional"))); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/NpmUpdateCommand.cs b/Nodejs/Product/Npm/SPI/NpmUpdateCommand.cs index 3e8711e19..1d0dc1e51 100644 --- a/Nodejs/Product/Npm/SPI/NpmUpdateCommand.cs +++ b/Nodejs/Product/Npm/SPI/NpmUpdateCommand.cs @@ -1,48 +1,48 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; -using System.Text; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class NpmUpdateCommand : NpmCommand { - public NpmUpdateCommand(string fullPathToRootPackageDirectory, bool global, string pathToNpm = null) - : this(fullPathToRootPackageDirectory, new List(), global, pathToNpm) { } - - public NpmUpdateCommand( - string fullPathToRootPackageDirectory, - IEnumerable packages, - bool global, - string pathToNpm = null, - bool useFallbackIfNpmNotFound = true) - : base(fullPathToRootPackageDirectory, pathToNpm) { - var buff = new StringBuilder("update"); - if (global) { - buff.Append(" -g"); - } - - foreach (var package in packages) { - buff.Append(' '); - buff.Append(package.Name); - } - - if (!global) { - buff.Append(" --save"); - } - Arguments = buff.ToString(); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; +using System.Text; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class NpmUpdateCommand : NpmCommand { + public NpmUpdateCommand(string fullPathToRootPackageDirectory, bool global, string pathToNpm = null) + : this(fullPathToRootPackageDirectory, new List(), global, pathToNpm) { } + + public NpmUpdateCommand( + string fullPathToRootPackageDirectory, + IEnumerable packages, + bool global, + string pathToNpm = null, + bool useFallbackIfNpmNotFound = true) + : base(fullPathToRootPackageDirectory, pathToNpm) { + var buff = new StringBuilder("update"); + if (global) { + buff.Append(" -g"); + } + + foreach (var package in packages) { + buff.Append(' '); + buff.Append(package.Name); + } + + if (!global) { + buff.Append(" --save"); + } + Arguments = buff.ToString(); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Package.cs b/Nodejs/Product/Npm/SPI/Package.cs index 5280fa858..6415d8b8e 100644 --- a/Nodejs/Product/Npm/SPI/Package.cs +++ b/Nodejs/Product/Npm/SPI/Package.cs @@ -1,103 +1,103 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Package : RootPackage, IPackage { - private IRootPackage _parent; - - public Package( - IRootPackage parent, - string fullPathToRootDirectory, - bool showMissingDevOptionalSubPackages, - Dictionary allModules = null, - int depth = 0) - : base(fullPathToRootDirectory, showMissingDevOptionalSubPackages, allModules, depth) { - _parent = parent; - } - - public string PublishDateTimeString { get { return null; } } - - public IEnumerable AvailableVersions { get { throw new NotImplementedException(); } } - - public string RequestedVersionRange { get; internal set; } - - public IEnumerable Keywords { - get { - var keywords = null == PackageJson ? null : PackageJson.Keywords; - return keywords ?? (IEnumerable) new List(); - } - } - - public bool IsListedInParentPackageJson { - get { - IPackageJson parentPackageJson = _parent.PackageJson; - return _parent is IGlobalPackages || - (null != parentPackageJson && parentPackageJson.AllDependencies.Contains(Name)); - } - } - - public bool IsMissing { - get { return IsListedInParentPackageJson && !Directory.Exists(Path); } - } - - public bool IsDevDependency { - get { - IPackageJson parentPackageJson = _parent.PackageJson; - return null != parentPackageJson && parentPackageJson.DevDependencies.Contains(Name); - } - } - - public bool IsDependency { - get { - IPackageJson parentPackageJson = _parent.PackageJson; - return null != parentPackageJson && parentPackageJson.Dependencies.Contains(Name); - } - } - - public bool IsOptionalDependency { - get { - IPackageJson parentPackageJson = _parent.PackageJson; - return null != parentPackageJson && parentPackageJson.OptionalDependencies.Contains(Name); - } - } - - public bool IsBundledDependency { - get { - IPackageJson parentPackageJson = _parent.PackageJson; - return null != parentPackageJson && parentPackageJson.BundledDependencies.Contains(Name); - } - } - - public PackageFlags Flags { - get { - return (!IsListedInParentPackageJson ? PackageFlags.NotListedAsDependency : 0) - | (IsMissing ? PackageFlags.Missing : 0) - | (IsDevDependency ? PackageFlags.Dev : 0) - | (IsOptionalDependency ? PackageFlags.Optional : 0) - | (IsBundledDependency ? PackageFlags.Bundled : 0); - } - } - - public override string ToString() { - return string.Format("{0} {1}", Name, Version); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Package : RootPackage, IPackage { + private IRootPackage _parent; + + public Package( + IRootPackage parent, + string fullPathToRootDirectory, + bool showMissingDevOptionalSubPackages, + Dictionary allModules = null, + int depth = 0) + : base(fullPathToRootDirectory, showMissingDevOptionalSubPackages, allModules, depth) { + _parent = parent; + } + + public string PublishDateTimeString { get { return null; } } + + public IEnumerable AvailableVersions { get { throw new NotImplementedException(); } } + + public string RequestedVersionRange { get; internal set; } + + public IEnumerable Keywords { + get { + var keywords = null == PackageJson ? null : PackageJson.Keywords; + return keywords ?? (IEnumerable) new List(); + } + } + + public bool IsListedInParentPackageJson { + get { + IPackageJson parentPackageJson = _parent.PackageJson; + return _parent is IGlobalPackages || + (null != parentPackageJson && parentPackageJson.AllDependencies.Contains(Name)); + } + } + + public bool IsMissing { + get { return IsListedInParentPackageJson && !Directory.Exists(Path); } + } + + public bool IsDevDependency { + get { + IPackageJson parentPackageJson = _parent.PackageJson; + return null != parentPackageJson && parentPackageJson.DevDependencies.Contains(Name); + } + } + + public bool IsDependency { + get { + IPackageJson parentPackageJson = _parent.PackageJson; + return null != parentPackageJson && parentPackageJson.Dependencies.Contains(Name); + } + } + + public bool IsOptionalDependency { + get { + IPackageJson parentPackageJson = _parent.PackageJson; + return null != parentPackageJson && parentPackageJson.OptionalDependencies.Contains(Name); + } + } + + public bool IsBundledDependency { + get { + IPackageJson parentPackageJson = _parent.PackageJson; + return null != parentPackageJson && parentPackageJson.BundledDependencies.Contains(Name); + } + } + + public PackageFlags Flags { + get { + return (!IsListedInParentPackageJson ? PackageFlags.NotListedAsDependency : 0) + | (IsMissing ? PackageFlags.Missing : 0) + | (IsDevDependency ? PackageFlags.Dev : 0) + | (IsOptionalDependency ? PackageFlags.Optional : 0) + | (IsBundledDependency ? PackageFlags.Bundled : 0); + } + } + + public override string ToString() { + return string.Format("{0} {1}", Name, Version); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/PackageJson.cs b/Nodejs/Product/Npm/SPI/PackageJson.cs index a80f62f02..8f228c707 100644 --- a/Nodejs/Product/Npm/SPI/PackageJson.cs +++ b/Nodejs/Product/Npm/SPI/PackageJson.cs @@ -1,231 +1,231 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Linq; -using System.Collections.Generic; -using Microsoft.CSharp.RuntimeBinder; -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class PackageJson : IPackageJson { - private dynamic _package; - private Scripts _scripts; - private Bugs _bugs; - - public PackageJson(dynamic package) { - _package = package; - - InitKeywords(); - InitHomepages(); - InitLicenses(); - InitFiles(); - InitMan(); - InitDependencies(); - InitDevDependencies(); - InitBundledDependencies(); - InitOptionalDependencies(); - InitAllDependencies(); - InitRequiredBy(); - } - - private void WrapRuntimeBinderExceptionAndRethrow( - string errorProperty, - RuntimeBinderException rbe) { - throw new PackageJsonException( - string.Format(@"Exception occurred retrieving {0} from package.json. The file may be invalid: you should edit it to correct an errors. - -The following error occurred: - -{1}", - errorProperty, - rbe)); - } - - private void InitKeywords() { - try { - Keywords = new Keywords(_package); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "keywords", - rbe); - } - } - - private void InitHomepages() { - try { - Homepages = new Homepages(_package); - } - catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "homepage", - rbe); - } - } - - private void InitFiles() { - try { - Files = new PkgFiles(_package); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "files", - rbe); - } - } - - private void InitLicenses() { - try { - Licenses = new Licenses(_package); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "licenses", - rbe); - } - } - - private void InitMan() { - try { - Man = new Man(_package); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "man", - rbe); - } - } - - private void InitDependencies() { - try { - Dependencies = new Dependencies(_package, "dependencies"); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "dependencies", - rbe); - } - } - - private void InitDevDependencies() { - try { - DevDependencies = new Dependencies(_package, "devDependencies"); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "dev dependencies", - rbe); - } - } - - private void InitBundledDependencies() { - try { - BundledDependencies = new BundledDependencies(_package); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "bundled dependencies", - rbe); - } - } - - private void InitOptionalDependencies() { - try { - OptionalDependencies = new Dependencies(_package, "optionalDependencies"); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "optional dependencies", - rbe); - } - } - - private void InitAllDependencies() { - try { - AllDependencies = new Dependencies(_package, "dependencies", "devDependencies", "optionalDependencies"); - } catch (RuntimeBinderException rbe) { - WrapRuntimeBinderExceptionAndRethrow( - "all dependencies", - rbe); - } - } - - private void InitRequiredBy() { - try { - RequiredBy = (_package["_requiredBy"] as IEnumerable ?? Enumerable.Empty()).Values(); - } catch (RuntimeBinderException rbe) { - System.Diagnostics.Debug.WriteLine(rbe); - WrapRuntimeBinderExceptionAndRethrow( - "required by", - rbe); - } - } - - public string Name { - get { return null == _package.name ? null : _package.name.ToString(); } - } - - public SemverVersion Version { - get { - return null == _package.version ? new SemverVersion() : SemverVersion.Parse(_package.version.ToString()); - } - } - - public IScripts Scripts { - get { - if (null == _scripts) { - dynamic scriptsJson = _package.scripts; - if (null == scriptsJson) { - scriptsJson = new JObject(); - _package.scripts = scriptsJson; - } - _scripts = new Scripts(scriptsJson); - } - - return _scripts; - } - } - - public IPerson Author { - get { - var author = _package.author; - return null == author ? null : new Person(author.ToString()); - } - } - - public string Description { - get { return null == _package.description ? null : _package.description.ToString(); } - } - - public IKeywords Keywords { get; private set; } - - public IHomepages Homepages { get; private set; } - - public IBugs Bugs { - get { - if (null == _bugs && null != _package.bugs) { - _bugs = new Bugs(_package); - } - return _bugs; - } - } - - public ILicenses Licenses { get; private set; } - - public IFiles Files { get; private set; } - - public IMan Man { get; private set; } - - public IDependencies Dependencies { get; private set; } - public IDependencies DevDependencies { get; private set; } - public IBundledDependencies BundledDependencies { get; private set; } - public IDependencies OptionalDependencies { get; private set; } - public IDependencies AllDependencies { get; private set; } - public IEnumerable RequiredBy { get; private set; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Linq; +using System.Collections.Generic; +using Microsoft.CSharp.RuntimeBinder; +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class PackageJson : IPackageJson { + private dynamic _package; + private Scripts _scripts; + private Bugs _bugs; + + public PackageJson(dynamic package) { + _package = package; + + InitKeywords(); + InitHomepages(); + InitLicenses(); + InitFiles(); + InitMan(); + InitDependencies(); + InitDevDependencies(); + InitBundledDependencies(); + InitOptionalDependencies(); + InitAllDependencies(); + InitRequiredBy(); + } + + private void WrapRuntimeBinderExceptionAndRethrow( + string errorProperty, + RuntimeBinderException rbe) { + throw new PackageJsonException( + string.Format(@"Exception occurred retrieving {0} from package.json. The file may be invalid: you should edit it to correct an errors. + +The following error occurred: + +{1}", + errorProperty, + rbe)); + } + + private void InitKeywords() { + try { + Keywords = new Keywords(_package); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "keywords", + rbe); + } + } + + private void InitHomepages() { + try { + Homepages = new Homepages(_package); + } + catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "homepage", + rbe); + } + } + + private void InitFiles() { + try { + Files = new PkgFiles(_package); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "files", + rbe); + } + } + + private void InitLicenses() { + try { + Licenses = new Licenses(_package); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "licenses", + rbe); + } + } + + private void InitMan() { + try { + Man = new Man(_package); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "man", + rbe); + } + } + + private void InitDependencies() { + try { + Dependencies = new Dependencies(_package, "dependencies"); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "dependencies", + rbe); + } + } + + private void InitDevDependencies() { + try { + DevDependencies = new Dependencies(_package, "devDependencies"); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "dev dependencies", + rbe); + } + } + + private void InitBundledDependencies() { + try { + BundledDependencies = new BundledDependencies(_package); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "bundled dependencies", + rbe); + } + } + + private void InitOptionalDependencies() { + try { + OptionalDependencies = new Dependencies(_package, "optionalDependencies"); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "optional dependencies", + rbe); + } + } + + private void InitAllDependencies() { + try { + AllDependencies = new Dependencies(_package, "dependencies", "devDependencies", "optionalDependencies"); + } catch (RuntimeBinderException rbe) { + WrapRuntimeBinderExceptionAndRethrow( + "all dependencies", + rbe); + } + } + + private void InitRequiredBy() { + try { + RequiredBy = (_package["_requiredBy"] as IEnumerable ?? Enumerable.Empty()).Values(); + } catch (RuntimeBinderException rbe) { + System.Diagnostics.Debug.WriteLine(rbe); + WrapRuntimeBinderExceptionAndRethrow( + "required by", + rbe); + } + } + + public string Name { + get { return null == _package.name ? null : _package.name.ToString(); } + } + + public SemverVersion Version { + get { + return null == _package.version ? new SemverVersion() : SemverVersion.Parse(_package.version.ToString()); + } + } + + public IScripts Scripts { + get { + if (null == _scripts) { + dynamic scriptsJson = _package.scripts; + if (null == scriptsJson) { + scriptsJson = new JObject(); + _package.scripts = scriptsJson; + } + _scripts = new Scripts(scriptsJson); + } + + return _scripts; + } + } + + public IPerson Author { + get { + var author = _package.author; + return null == author ? null : new Person(author.ToString()); + } + } + + public string Description { + get { return null == _package.description ? null : _package.description.ToString(); } + } + + public IKeywords Keywords { get; private set; } + + public IHomepages Homepages { get; private set; } + + public IBugs Bugs { + get { + if (null == _bugs && null != _package.bugs) { + _bugs = new Bugs(_package); + } + return _bugs; + } + } + + public ILicenses Licenses { get; private set; } + + public IFiles Files { get; private set; } + + public IMan Man { get; private set; } + + public IDependencies Dependencies { get; private set; } + public IDependencies DevDependencies { get; private set; } + public IBundledDependencies BundledDependencies { get; private set; } + public IDependencies OptionalDependencies { get; private set; } + public IDependencies AllDependencies { get; private set; } + public IEnumerable RequiredBy { get; private set; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/PackageProxy.cs b/Nodejs/Product/Npm/SPI/PackageProxy.cs index a10546b35..5b11408e8 100644 --- a/Nodejs/Product/Npm/SPI/PackageProxy.cs +++ b/Nodejs/Product/Npm/SPI/PackageProxy.cs @@ -1,61 +1,61 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class PackageProxy : IPackage { - public INodeModules Modules { get; internal set; } - - public IPackageJson PackageJson { get; internal set; } - - public bool HasPackageJson { get; internal set; } - - public string Name { get; internal set; } - - public SemverVersion Version { get; internal set; } - - public IEnumerable AvailableVersions { get; internal set; } - - public IPerson Author { get; internal set; } - - public string Description { get; internal set; } - - public IEnumerable Homepages { get; internal set; } - - public string Path { get; internal set; } - - public string PublishDateTimeString { get; internal set; } - - public string RequestedVersionRange { get; internal set; } - - public IEnumerable Keywords { get; internal set; } - - public bool IsListedInParentPackageJson { get; internal set; } - - public bool IsMissing { get; internal set; } - - public bool IsDependency { get; internal set; } - - public bool IsDevDependency { get; internal set; } - - public bool IsOptionalDependency { get; internal set; } - - public bool IsBundledDependency { get; internal set; } - - public PackageFlags Flags { get; internal set; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class PackageProxy : IPackage { + public INodeModules Modules { get; internal set; } + + public IPackageJson PackageJson { get; internal set; } + + public bool HasPackageJson { get; internal set; } + + public string Name { get; internal set; } + + public SemverVersion Version { get; internal set; } + + public IEnumerable AvailableVersions { get; internal set; } + + public IPerson Author { get; internal set; } + + public string Description { get; internal set; } + + public IEnumerable Homepages { get; internal set; } + + public string Path { get; internal set; } + + public string PublishDateTimeString { get; internal set; } + + public string RequestedVersionRange { get; internal set; } + + public IEnumerable Keywords { get; internal set; } + + public bool IsListedInParentPackageJson { get; internal set; } + + public bool IsMissing { get; internal set; } + + public bool IsDependency { get; internal set; } + + public bool IsDevDependency { get; internal set; } + + public bool IsOptionalDependency { get; internal set; } + + public bool IsBundledDependency { get; internal set; } + + public PackageFlags Flags { get; internal set; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Person.cs b/Nodejs/Product/Npm/SPI/Person.cs index a9fb29f3b..cdbe3f674 100644 --- a/Nodejs/Product/Npm/SPI/Person.cs +++ b/Nodejs/Product/Npm/SPI/Person.cs @@ -1,131 +1,131 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Diagnostics; -using System.Text; -using System.Text.RegularExpressions; -using Newtonsoft.Json; - -#if DEBUG -using Newtonsoft.Json.Linq; -#endif - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Person : IPerson { - - // We cannot rely on the ordering of any of these fields, - // so we should match them separately. - private static readonly Regex RegexPerson = new Regex( - "\"name\":\\s*\"(?[^<]+?)\"" + - "|" + - "\"email\":\\s*\"(?[^<]+?)\"" + - "|" + - "\"url\":\\s*\"(?[^<]+?)\"", - RegexOptions.Singleline); - - [JsonConstructor] - private Person() { - // Enables Json deserialization - } - - public Person(string source) { - InitFromString(source); - } - - private void InitFromString(string source) { - if (source == null) { - Name = string.Empty; - return; - } - - // We parse using a regex because JObject.Parse throws exceptions for malformatted json, - // and simply handling them causes performance issues. - var matches = RegexPerson.Matches(source); - if (matches.Count >= 1) { - foreach (Match match in matches) { - var group = match.Groups["name"]; - if (group.Success) { - Name = group.Value; - continue; - } - - group = match.Groups["email"]; - if (group.Success) { - Email = group.Value; - continue; - } - - group = match.Groups["url"]; - if (group.Success) { - Url = group.Value; - continue; - } - } - } else { - Name = source; - } - -#if DEBUG - // Verify we are parsing correctly - try { - var jObject = JObject.Parse(source); - var name = (string)jObject["name"]; - Debug.Assert(name != null ? name == Name : Name == source, string.Format("Failed to parse name from {0}", source)); - Debug.Assert((string)jObject["email"] == Email, string.Format("Failed to parse email from {0}", source)); - Debug.Assert((string)jObject["url"] == Url, string.Format("Failed to parse url from {0}", source)); - } catch (Exception) { - Debug.Assert(source == Name); - } -#endif - } - - [JsonProperty] - public string Name { get; private set; } - - [JsonProperty] - public string Email { get; private set; } - - [JsonProperty] - public string Url { get; private set; } - - public override string ToString() { - var buff = new StringBuilder(); - if (!string.IsNullOrEmpty(Name)) { - buff.Append(Name); - } - - if (!string.IsNullOrEmpty(Email)) { - if (buff.Length > 0) { - buff.Append(' '); - } - buff.Append('<'); - buff.Append(Email); - buff.Append('>'); - } - - if (!string.IsNullOrEmpty(Url)) { - if (buff.Length > 0) { - buff.Append(' '); - } - buff.Append('('); - buff.Append(Url); - buff.Append(')'); - } - return buff.ToString(); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; + +#if DEBUG +using Newtonsoft.Json.Linq; +#endif + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Person : IPerson { + + // We cannot rely on the ordering of any of these fields, + // so we should match them separately. + private static readonly Regex RegexPerson = new Regex( + "\"name\":\\s*\"(?[^<]+?)\"" + + "|" + + "\"email\":\\s*\"(?[^<]+?)\"" + + "|" + + "\"url\":\\s*\"(?[^<]+?)\"", + RegexOptions.Singleline); + + [JsonConstructor] + private Person() { + // Enables Json deserialization + } + + public Person(string source) { + InitFromString(source); + } + + private void InitFromString(string source) { + if (source == null) { + Name = string.Empty; + return; + } + + // We parse using a regex because JObject.Parse throws exceptions for malformatted json, + // and simply handling them causes performance issues. + var matches = RegexPerson.Matches(source); + if (matches.Count >= 1) { + foreach (Match match in matches) { + var group = match.Groups["name"]; + if (group.Success) { + Name = group.Value; + continue; + } + + group = match.Groups["email"]; + if (group.Success) { + Email = group.Value; + continue; + } + + group = match.Groups["url"]; + if (group.Success) { + Url = group.Value; + continue; + } + } + } else { + Name = source; + } + +#if DEBUG + // Verify we are parsing correctly + try { + var jObject = JObject.Parse(source); + var name = (string)jObject["name"]; + Debug.Assert(name != null ? name == Name : Name == source, string.Format("Failed to parse name from {0}", source)); + Debug.Assert((string)jObject["email"] == Email, string.Format("Failed to parse email from {0}", source)); + Debug.Assert((string)jObject["url"] == Url, string.Format("Failed to parse url from {0}", source)); + } catch (Exception) { + Debug.Assert(source == Name); + } +#endif + } + + [JsonProperty] + public string Name { get; private set; } + + [JsonProperty] + public string Email { get; private set; } + + [JsonProperty] + public string Url { get; private set; } + + public override string ToString() { + var buff = new StringBuilder(); + if (!string.IsNullOrEmpty(Name)) { + buff.Append(Name); + } + + if (!string.IsNullOrEmpty(Email)) { + if (buff.Length > 0) { + buff.Append(' '); + } + buff.Append('<'); + buff.Append(Email); + buff.Append('>'); + } + + if (!string.IsNullOrEmpty(Url)) { + if (buff.Length > 0) { + buff.Append(' '); + } + buff.Append('('); + buff.Append(Url); + buff.Append(')'); + } + return buff.ToString(); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/PkgFiles.cs b/Nodejs/Product/Npm/SPI/PkgFiles.cs index 595cc4206..85c451474 100644 --- a/Nodejs/Product/Npm/SPI/PkgFiles.cs +++ b/Nodejs/Product/Npm/SPI/PkgFiles.cs @@ -1,23 +1,23 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class PkgFiles : PkgStringArray, IFiles { - public PkgFiles(JObject package) : base(package, "files") { } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class PkgFiles : PkgStringArray, IFiles { + public PkgFiles(JObject package) : base(package, "files") { } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/PkgStringArray.cs b/Nodejs/Product/Npm/SPI/PkgStringArray.cs index 2d7146ed1..0eef8c0a7 100644 --- a/Nodejs/Product/Npm/SPI/PkgStringArray.cs +++ b/Nodejs/Product/Npm/SPI/PkgStringArray.cs @@ -1,107 +1,107 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal abstract class PkgStringArray : IPkgStringArray { - private readonly JObject _package; - private readonly string[] arrayPropertyNames; - - protected PkgStringArray(JObject package, params string[] arrayPropertyNames) { - _package = package; - this.arrayPropertyNames = arrayPropertyNames; - } - - private JToken GetArrayProperty() { - foreach (var name in arrayPropertyNames) { - var array = _package[name] as JToken; - if (null != array) { - return array; - } - } - return null; - } - - public int Count { - get { - var token = GetArrayProperty(); - if (null == token) { - return 0; - } - - switch (token.Type) { - case JTokenType.String: - return 1; - - case JTokenType.Array: - return (token as JArray).Count; - - default: - return 0; - } - } - } - - public string this[int index] { - get { - if (Count <= 0) { - throw new IndexOutOfRangeException( - "Cannot retrieve item from empty package.json string array."); - } - - var token = GetArrayProperty(); - switch (token.Type) { - case JTokenType.String: - if (index != 0) { - throw new IndexOutOfRangeException( - string.Format( - "Cannot retrieve value from index '{0}' in a package.json string array containing only 1 item.", - index)); - } - return token.Value(); - - default: // Can only be an array at this point, since Count has been called. - return (token as JArray)[index].Value(); - } - } - } - - public IEnumerator GetEnumerator() { - switch (Count) { - case 0: - return new List().GetEnumerator(); - - case 1: - return new List { this[0] }.GetEnumerator(); - - default: - return - (GetArrayProperty() as JArray).Select(value => value.Value()) - .ToList() - .GetEnumerator(); - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return GetEnumerator(); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal abstract class PkgStringArray : IPkgStringArray { + private readonly JObject _package; + private readonly string[] arrayPropertyNames; + + protected PkgStringArray(JObject package, params string[] arrayPropertyNames) { + _package = package; + this.arrayPropertyNames = arrayPropertyNames; + } + + private JToken GetArrayProperty() { + foreach (var name in arrayPropertyNames) { + var array = _package[name] as JToken; + if (null != array) { + return array; + } + } + return null; + } + + public int Count { + get { + var token = GetArrayProperty(); + if (null == token) { + return 0; + } + + switch (token.Type) { + case JTokenType.String: + return 1; + + case JTokenType.Array: + return (token as JArray).Count; + + default: + return 0; + } + } + } + + public string this[int index] { + get { + if (Count <= 0) { + throw new IndexOutOfRangeException( + "Cannot retrieve item from empty package.json string array."); + } + + var token = GetArrayProperty(); + switch (token.Type) { + case JTokenType.String: + if (index != 0) { + throw new IndexOutOfRangeException( + string.Format( + "Cannot retrieve value from index '{0}' in a package.json string array containing only 1 item.", + index)); + } + return token.Value(); + + default: // Can only be an array at this point, since Count has been called. + return (token as JArray)[index].Value(); + } + } + } + + public IEnumerator GetEnumerator() { + switch (Count) { + case 0: + return new List().GetEnumerator(); + + case 1: + return new List { this[0] }.GetEnumerator(); + + default: + return + (GetArrayProperty() as JArray).Select(value => value.Value()) + .ToList() + .GetEnumerator(); + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return GetEnumerator(); + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/RootPackage.cs b/Nodejs/Product/Npm/SPI/RootPackage.cs index af9b3bbf0..cd89507b1 100644 --- a/Nodejs/Product/Npm/SPI/RootPackage.cs +++ b/Nodejs/Product/Npm/SPI/RootPackage.cs @@ -1,84 +1,84 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.IO; -using Microsoft.CSharp.RuntimeBinder; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class RootPackage : IRootPackage { - public RootPackage( - string fullPathToRootDirectory, - bool showMissingDevOptionalSubPackages, - Dictionary allModules = null, - int depth = 0) { - Path = fullPathToRootDirectory; - var packageJsonFile = System.IO.Path.Combine(fullPathToRootDirectory, "package.json"); - try { - if (packageJsonFile.Length < 260) { - PackageJson = PackageJsonFactory.Create(new DirectoryPackageJsonSource(fullPathToRootDirectory)); - } - } catch (RuntimeBinderException rbe) { - throw new PackageJsonException( - string.Format(@"Error processing package.json at '{0}'. The file was successfully read, and may be valid JSON, but the objects may not match the expected form for a package.json file. - -The following error was reported: - -{1}", - packageJsonFile, - rbe.Message), - rbe); - } - - try { - Modules = new NodeModules(this, showMissingDevOptionalSubPackages, allModules, depth); - } catch (PathTooLongException) { - // otherwise we fail to create it completely... - } - } - - public IPackageJson PackageJson { get; private set; } - - public bool HasPackageJson { - get { return null != PackageJson; } - } - - public string Name { - get { return null == PackageJson ? new DirectoryInfo(Path).Name : PackageJson.Name; } - } - - public SemverVersion Version { - get { return null == PackageJson ? new SemverVersion() : PackageJson.Version; } - } - - public IPerson Author { - get { return null == PackageJson ? null : PackageJson.Author; } - } - - public string Description { - get { return null == PackageJson ? null : PackageJson.Description; } - } - - public IEnumerable Homepages { - get { return null == PackageJson ? null : PackageJson.Homepages; } - } - - public string Path { get; private set; } - - public INodeModules Modules { get; private set; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.CSharp.RuntimeBinder; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class RootPackage : IRootPackage { + public RootPackage( + string fullPathToRootDirectory, + bool showMissingDevOptionalSubPackages, + Dictionary allModules = null, + int depth = 0) { + Path = fullPathToRootDirectory; + var packageJsonFile = System.IO.Path.Combine(fullPathToRootDirectory, "package.json"); + try { + if (packageJsonFile.Length < 260) { + PackageJson = PackageJsonFactory.Create(new DirectoryPackageJsonSource(fullPathToRootDirectory)); + } + } catch (RuntimeBinderException rbe) { + throw new PackageJsonException( + string.Format(@"Error processing package.json at '{0}'. The file was successfully read, and may be valid JSON, but the objects may not match the expected form for a package.json file. + +The following error was reported: + +{1}", + packageJsonFile, + rbe.Message), + rbe); + } + + try { + Modules = new NodeModules(this, showMissingDevOptionalSubPackages, allModules, depth); + } catch (PathTooLongException) { + // otherwise we fail to create it completely... + } + } + + public IPackageJson PackageJson { get; private set; } + + public bool HasPackageJson { + get { return null != PackageJson; } + } + + public string Name { + get { return null == PackageJson ? new DirectoryInfo(Path).Name : PackageJson.Name; } + } + + public SemverVersion Version { + get { return null == PackageJson ? new SemverVersion() : PackageJson.Version; } + } + + public IPerson Author { + get { return null == PackageJson ? null : PackageJson.Author; } + } + + public string Description { + get { return null == PackageJson ? null : PackageJson.Description; } + } + + public IEnumerable Homepages { + get { return null == PackageJson ? null : PackageJson.Homepages; } + } + + public string Path { get; private set; } + + public INodeModules Modules { get; private set; } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Script.cs b/Nodejs/Product/Npm/SPI/Script.cs index 3a9a33be9..c16234d87 100644 --- a/Nodejs/Product/Npm/SPI/Script.cs +++ b/Nodejs/Product/Npm/SPI/Script.cs @@ -1,32 +1,32 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Script : IScript { - private dynamic _code; - - public Script(string name, dynamic code) { - Name = name; - _code = code; - } - - public string Name { get; private set; } - - public string Code { - get { return _code.ToString(); } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Script : IScript { + private dynamic _code; + + public Script(string name, dynamic code) { + Name = name; + _code = code; + } + + public string Name { get; private set; } + + public string Code { + get { return _code.ToString(); } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SPI/Scripts.cs b/Nodejs/Product/Npm/SPI/Scripts.cs index 3690b113d..e43dbf8f9 100644 --- a/Nodejs/Product/Npm/SPI/Scripts.cs +++ b/Nodejs/Product/Npm/SPI/Scripts.cs @@ -1,45 +1,45 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Newtonsoft.Json.Linq; - -namespace Microsoft.NodejsTools.Npm.SPI { - internal class Scripts : IScripts { - private dynamic _scripts; - - public Scripts(dynamic scripts) { - _scripts = scripts; - } - - public int Count { - get { - JObject temp = _scripts; - return null == temp ? 0 : temp.Count; - } - } - - public IScript this[string name] { - get { - IScript script = null; - dynamic json = _scripts[name]; - if (null != json) { - script = new Script(name, json); - } - return script; - } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Newtonsoft.Json.Linq; + +namespace Microsoft.NodejsTools.Npm.SPI { + internal class Scripts : IScripts { + private dynamic _scripts; + + public Scripts(dynamic scripts) { + _scripts = scripts; + } + + public int Count { + get { + JObject temp = _scripts; + return null == temp ? 0 : temp.Count; + } + } + + public IScript this[string name] { + get { + IScript script = null; + dynamic json = _scripts[name]; + if (null != json) { + script = new Script(name, json); + } + return script; + } + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/ScriptName.cs b/Nodejs/Product/Npm/ScriptName.cs index 7df52020d..1057b4aac 100644 --- a/Nodejs/Product/Npm/ScriptName.cs +++ b/Nodejs/Product/Npm/ScriptName.cs @@ -1,44 +1,44 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -namespace Microsoft.NodejsTools.Npm { - public static class ScriptName { - public const string Prepublish = "prepublish"; - public const string Publish = "publish"; - public const string Postpublish = "postpublish"; - public const string Preinstall = "preinstall"; - public const string Install = "install"; - public const string Postinstall = "postinstall"; - public const string Preuninstall = "preuninstall"; - public const string Uninstall = "uninstall"; - public const string Postuninstall = "postuninstall"; - public const string Preupdate = "preupdate"; - public const string Update = "update"; - public const string Postupdate = "postupdate"; - public const string Pretest = "pretest"; - public const string Test = "test"; - public const string Posttest = "posttest"; - public const string Prestop = "prestop"; - public const string Stop = "stop"; - public const string Poststop = "poststop"; - public const string Prestart = "prestart"; - public const string Start = "start"; - public const string Poststart = "poststart"; - public const string Prerestart = "prerestart"; - public const string Restart = "restart"; - public const string Postrestart = "postrestart"; - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +namespace Microsoft.NodejsTools.Npm { + public static class ScriptName { + public const string Prepublish = "prepublish"; + public const string Publish = "publish"; + public const string Postpublish = "postpublish"; + public const string Preinstall = "preinstall"; + public const string Install = "install"; + public const string Postinstall = "postinstall"; + public const string Preuninstall = "preuninstall"; + public const string Uninstall = "uninstall"; + public const string Postuninstall = "postuninstall"; + public const string Preupdate = "preupdate"; + public const string Update = "update"; + public const string Postupdate = "postupdate"; + public const string Pretest = "pretest"; + public const string Test = "test"; + public const string Posttest = "posttest"; + public const string Prestop = "prestop"; + public const string Stop = "stop"; + public const string Poststop = "poststop"; + public const string Prestart = "prestart"; + public const string Start = "start"; + public const string Poststart = "poststart"; + public const string Prerestart = "prerestart"; + public const string Restart = "restart"; + public const string Postrestart = "postrestart"; + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SemverVersion.cs b/Nodejs/Product/Npm/SemverVersion.cs index 8105b71c4..e221c75a9 100644 --- a/Nodejs/Product/Npm/SemverVersion.cs +++ b/Nodejs/Product/Npm/SemverVersion.cs @@ -1,203 +1,203 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Text; -using System.Text.RegularExpressions; -using Newtonsoft.Json; - -namespace Microsoft.NodejsTools.Npm { - /// - /// Represents a semantic version as defined at http://semver.org/ - /// and used by the npm semantic versioner: https://npmjs.org/doc/misc/semver.html. - /// - public struct SemverVersion { - public static readonly SemverVersion UnknownVersion = new SemverVersion(0, 0, 0); - - private static readonly Regex RegexSemver = new Regex( - "^(?[0-9]+)" - + "\\.(?[0-9]+)" - + "\\.(?[…0-9]+)" // The '…' is there to handle the 'classy' library, which has a very long version number - see slightly snarky comment about that unadulterated bag of hilarity below. - + "(?:-(?[…0-9A-Za-z-]+(\\.[…0-9A-Za-z-]+)*))?" - + "(?:\\+(?[…0-9A-Za-z-]+(\\.[…0-9A-Za-z-]+)*))?$", - RegexOptions.Singleline); - - private static readonly Regex RegexOptionalFragment = new Regex( - "^[…0-9A-Za-z-]+(\\.[…0-9A-Za-z-]+)*$", - RegexOptions.Singleline); - - public static SemverVersion Parse(string versionString) { - var matches = RegexSemver.Matches(versionString); - if (matches.Count != 1) { - throw new SemverVersionFormatException( - string.Format( - "Invalid semantic version: '{0}'. The version number must consist of three non-negative numeric parts of the form MAJOR.MINOR.PATCH, with optional pre-release and/or build metadata. The optional parts may only contain characters in the set [0-9A-Za-z-].", - versionString)); - } - - var match = matches[0]; - var preRelease = match.Groups["prerelease"]; - var buildMetadata = match.Groups["buildmetadata"]; - - try { - // Hack: To deal with patch truncation - e.g., seen with 'classy' package in npm v1.4.3 onwards - var patch = match.Groups["patch"].Value; - while (!string.IsNullOrEmpty(patch) && patch.EndsWith("…")) { - patch = patch.Length == 1 ? "0" : patch.Substring(0, patch.Length - 1); - } - // /Hack - - return new SemverVersion( - ulong.Parse(match.Groups["major"].Value), - ulong.Parse(match.Groups["minor"].Value), - ulong.Parse(patch), - preRelease.Success ? preRelease.Value : null, - buildMetadata.Success ? buildMetadata.Value : null); - } catch (OverflowException oe) { - throw new SemverVersionFormatException( - string.Format( - "Invalid semantic version: '{0}'. One or more of the integer parts is large enough to overflow a 64-bit int.", - versionString), - oe); - } - } - - // You may very well ask why these are now all ulongs. Well, you can thank the author of the so-called - // classy package for that. He saw fit to give his package a version number of 0.3.130506190513602. - // Wait! What?!? Does that mean there have been 130506190513601 previous patch releases of classy 0.3? - // No. No it doesn't. It means he can't read the semver spec. - - private static bool IsValidOptionalFragment(string optional) { - return string.IsNullOrEmpty(optional) || RegexOptionalFragment.IsMatch(optional); - } - - public SemverVersion( - ulong major, - ulong minor, - ulong patch, - string preReleaseVersion = null, - string buildMetadata = null) : this() { - if (!IsValidOptionalFragment(preReleaseVersion)) { - throw new ArgumentException( - string.Format( - "Invalid pre-release version: '{0}'. Must be a dot separated sequence of identifiers containing only characters [0-9A-Za-z-].", - preReleaseVersion), - "preReleaseVersion"); - } - - if (!IsValidOptionalFragment(buildMetadata)) { - throw new ArgumentException( - string.Format( - "Invalid build metadata: '{0}'. Must be a dot separated sequence of identifiers containing only characters [0-9A-Za-z-].", - preReleaseVersion), - "buildMetadata"); - } - - Major = major; - Minor = minor; - Patch = patch; - PreReleaseVersion = preReleaseVersion; - BuildMetadata = buildMetadata; - } - - [JsonProperty] - public ulong Major { get; private set; } - - [JsonProperty] - public ulong Minor { get; private set; } - - [JsonProperty] - public ulong Patch { get; private set; } - - - // N.B. Both PreReleaseVersion and BuildMetadata are series of dot separated identifiers, but since we don't really particularly - // care about them at the moment, can defer comparisons to semver, and won't need to do anything beyond - // let the user specify a value, I've just implemented them as strings. - [JsonProperty] - public string PreReleaseVersion { get; private set; } - - [JsonProperty] - public string BuildMetadata { get; private set; } - - public bool HasPreReleaseVersion { - get { return !string.IsNullOrEmpty(PreReleaseVersion); } - } - - public bool HasBuildMetadata { - get { return !string.IsNullOrEmpty(BuildMetadata); } - } - - public override string ToString() { - var builder = new StringBuilder(string.Format("{0}.{1}.{2}", Major, Minor, Patch)); - - if (HasPreReleaseVersion) { - builder.Append('-'); - builder.Append(PreReleaseVersion); - } - - if (HasBuildMetadata) { - builder.Append('+'); - builder.Append(BuildMetadata); - } - - return builder.ToString(); - } - - public override int GetHashCode() { - return ToString().GetHashCode(); - } - - public override bool Equals(object obj) { - if (!(obj is SemverVersion)) { - return false; - } - - var other = (SemverVersion)obj; - - // Note that we do NOT include build metadata in the comparison, - // since semver specifies that this is ignored when determining whether or - // not versions are equal. See point 11 at http://semver.org/. - return Major == other.Major - && Minor == other.Minor - && Patch == other.Patch - && PreReleaseVersion == other.PreReleaseVersion; - } - - public static bool operator ==(SemverVersion v1, SemverVersion v2) { - return v1.Equals(v2); - } - - public static bool operator !=(SemverVersion v1, SemverVersion v2) { - return !(v1 == v2); - } - - public static bool operator >(SemverVersion v1, SemverVersion v2) { - return new SemverVersionComparer().Compare(v1, v2) == 1; - } - - public static bool operator <(SemverVersion v1, SemverVersion v2) { - return new SemverVersionComparer().Compare(v1, v2) == -1; - } - - public static bool operator >=(SemverVersion v1, SemverVersion v2) { - return v1 == v2 || v1 > v2; - } - - public static bool operator <=(SemverVersion v1, SemverVersion v2) { - return v1 == v2 || v1 < v2; - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Text; +using System.Text.RegularExpressions; +using Newtonsoft.Json; + +namespace Microsoft.NodejsTools.Npm { + /// + /// Represents a semantic version as defined at http://semver.org/ + /// and used by the npm semantic versioner: https://npmjs.org/doc/misc/semver.html. + /// + public struct SemverVersion { + public static readonly SemverVersion UnknownVersion = new SemverVersion(0, 0, 0); + + private static readonly Regex RegexSemver = new Regex( + "^(?[0-9]+)" + + "\\.(?[0-9]+)" + + "\\.(?[…0-9]+)" // The '…' is there to handle the 'classy' library, which has a very long version number - see slightly snarky comment about that unadulterated bag of hilarity below. + + "(?:-(?[…0-9A-Za-z-]+(\\.[…0-9A-Za-z-]+)*))?" + + "(?:\\+(?[…0-9A-Za-z-]+(\\.[…0-9A-Za-z-]+)*))?$", + RegexOptions.Singleline); + + private static readonly Regex RegexOptionalFragment = new Regex( + "^[…0-9A-Za-z-]+(\\.[…0-9A-Za-z-]+)*$", + RegexOptions.Singleline); + + public static SemverVersion Parse(string versionString) { + var matches = RegexSemver.Matches(versionString); + if (matches.Count != 1) { + throw new SemverVersionFormatException( + string.Format( + "Invalid semantic version: '{0}'. The version number must consist of three non-negative numeric parts of the form MAJOR.MINOR.PATCH, with optional pre-release and/or build metadata. The optional parts may only contain characters in the set [0-9A-Za-z-].", + versionString)); + } + + var match = matches[0]; + var preRelease = match.Groups["prerelease"]; + var buildMetadata = match.Groups["buildmetadata"]; + + try { + // Hack: To deal with patch truncation - e.g., seen with 'classy' package in npm v1.4.3 onwards + var patch = match.Groups["patch"].Value; + while (!string.IsNullOrEmpty(patch) && patch.EndsWith("…")) { + patch = patch.Length == 1 ? "0" : patch.Substring(0, patch.Length - 1); + } + // /Hack + + return new SemverVersion( + ulong.Parse(match.Groups["major"].Value), + ulong.Parse(match.Groups["minor"].Value), + ulong.Parse(patch), + preRelease.Success ? preRelease.Value : null, + buildMetadata.Success ? buildMetadata.Value : null); + } catch (OverflowException oe) { + throw new SemverVersionFormatException( + string.Format( + "Invalid semantic version: '{0}'. One or more of the integer parts is large enough to overflow a 64-bit int.", + versionString), + oe); + } + } + + // You may very well ask why these are now all ulongs. Well, you can thank the author of the so-called + // classy package for that. He saw fit to give his package a version number of 0.3.130506190513602. + // Wait! What?!? Does that mean there have been 130506190513601 previous patch releases of classy 0.3? + // No. No it doesn't. It means he can't read the semver spec. + + private static bool IsValidOptionalFragment(string optional) { + return string.IsNullOrEmpty(optional) || RegexOptionalFragment.IsMatch(optional); + } + + public SemverVersion( + ulong major, + ulong minor, + ulong patch, + string preReleaseVersion = null, + string buildMetadata = null) : this() { + if (!IsValidOptionalFragment(preReleaseVersion)) { + throw new ArgumentException( + string.Format( + "Invalid pre-release version: '{0}'. Must be a dot separated sequence of identifiers containing only characters [0-9A-Za-z-].", + preReleaseVersion), + "preReleaseVersion"); + } + + if (!IsValidOptionalFragment(buildMetadata)) { + throw new ArgumentException( + string.Format( + "Invalid build metadata: '{0}'. Must be a dot separated sequence of identifiers containing only characters [0-9A-Za-z-].", + preReleaseVersion), + "buildMetadata"); + } + + Major = major; + Minor = minor; + Patch = patch; + PreReleaseVersion = preReleaseVersion; + BuildMetadata = buildMetadata; + } + + [JsonProperty] + public ulong Major { get; private set; } + + [JsonProperty] + public ulong Minor { get; private set; } + + [JsonProperty] + public ulong Patch { get; private set; } + + + // N.B. Both PreReleaseVersion and BuildMetadata are series of dot separated identifiers, but since we don't really particularly + // care about them at the moment, can defer comparisons to semver, and won't need to do anything beyond + // let the user specify a value, I've just implemented them as strings. + [JsonProperty] + public string PreReleaseVersion { get; private set; } + + [JsonProperty] + public string BuildMetadata { get; private set; } + + public bool HasPreReleaseVersion { + get { return !string.IsNullOrEmpty(PreReleaseVersion); } + } + + public bool HasBuildMetadata { + get { return !string.IsNullOrEmpty(BuildMetadata); } + } + + public override string ToString() { + var builder = new StringBuilder(string.Format("{0}.{1}.{2}", Major, Minor, Patch)); + + if (HasPreReleaseVersion) { + builder.Append('-'); + builder.Append(PreReleaseVersion); + } + + if (HasBuildMetadata) { + builder.Append('+'); + builder.Append(BuildMetadata); + } + + return builder.ToString(); + } + + public override int GetHashCode() { + return ToString().GetHashCode(); + } + + public override bool Equals(object obj) { + if (!(obj is SemverVersion)) { + return false; + } + + var other = (SemverVersion)obj; + + // Note that we do NOT include build metadata in the comparison, + // since semver specifies that this is ignored when determining whether or + // not versions are equal. See point 11 at http://semver.org/. + return Major == other.Major + && Minor == other.Minor + && Patch == other.Patch + && PreReleaseVersion == other.PreReleaseVersion; + } + + public static bool operator ==(SemverVersion v1, SemverVersion v2) { + return v1.Equals(v2); + } + + public static bool operator !=(SemverVersion v1, SemverVersion v2) { + return !(v1 == v2); + } + + public static bool operator >(SemverVersion v1, SemverVersion v2) { + return new SemverVersionComparer().Compare(v1, v2) == 1; + } + + public static bool operator <(SemverVersion v1, SemverVersion v2) { + return new SemverVersionComparer().Compare(v1, v2) == -1; + } + + public static bool operator >=(SemverVersion v1, SemverVersion v2) { + return v1 == v2 || v1 > v2; + } + + public static bool operator <=(SemverVersion v1, SemverVersion v2) { + return v1 == v2 || v1 < v2; + } + } } \ No newline at end of file diff --git a/Nodejs/Product/Npm/SemverVersionComparer.cs b/Nodejs/Product/Npm/SemverVersionComparer.cs index 55db5650c..b201e1bde 100644 --- a/Nodejs/Product/Npm/SemverVersionComparer.cs +++ b/Nodejs/Product/Npm/SemverVersionComparer.cs @@ -1,118 +1,118 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; - -namespace Microsoft.NodejsTools.Npm { - - /// - /// Performs semver version comparison in accordance with the rules of precedence - /// set out under point 11 at http://semver.org/. - /// - public class SemverVersionComparer : IComparer { - private bool IsNumericIdentifier(string identifier) { - for (int index = 0, size = identifier.Length; index < size; ++index) { - // If any non-digit is detected, or the first character is a leading 0, - // the identifier is treated as non-numeric - if (!char.IsDigit(identifier[index]) - || index == 0 && identifier[index] == '0') { - return false; - } - } - return true; - } - - private int ComparePreRelease(SemverVersion x, SemverVersion y) { - string xp = x.PreReleaseVersion, - yp = y.PreReleaseVersion; - - // Empty pre-release version has higher precedence than a value for pre-release version - if (string.IsNullOrEmpty(xp)) { - return string.IsNullOrEmpty(yp) ? 0 : 1; - } - - // Same as above; - if (string.IsNullOrEmpty(yp)) { - return -1; - } - - // Identifiers are separated by dots - string[] xs = xp.Split('.'), - ys = yp.Split('.'); - - // Compare identifiers individually until a difference is found - for (int index = 0, size = Math.Min(xs.Length, ys.Length); index < size; ++index) { - // Figure out which items in each pair of identifiers is numeric - has a profound - // impace on the subsequent comparison performed. - bool xn = IsNumericIdentifier(xs[index]), - yn = IsNumericIdentifier(ys[index]); - if (xn) { - if (yn) { - // Compare numeric identifiers in the expected way - var result = int.Parse(xs[index]).CompareTo(int.Parse(ys[index])); - if (0 != result) { - return result; - } - } else { - // Numeric identifiers have lower precedence than non-numeric, so y is greater - return -1; - } - } else if (yn) { - // Numeric identifiers have lower precedence than non-numeric, so x is greater - return 1; - } else { - var result = string.Compare(xs[index], ys[index], StringComparison.CurrentCulture); - if (0 != result) { - return result; - } - } - } - - // Still the same? More fields in pre-release information indicates higher precedence, - // otherwise identifiers are the same. - - if (xs.Length == ys.Length) { - return 0; - } - - if (xs.Length < ys.Length) { - return -1; - } - - return 1; - } - - public int Compare(SemverVersion x, SemverVersion y) { - // The version number comparisons are straightforward until - // we get into comparing pre-release information. In most cases - // this shouldn't be necessary. Note that build metadata is - // NOT included in any precedence comparison, and versions that - // differ only in build metadata should be treated as equivalent. - var result = x.Major.CompareTo(y.Major); - if (0 == result) { - result = x.Minor.CompareTo(y.Minor); - if (0 == result) { - result = x.Patch.CompareTo(y.Patch); - if (0 == result) { - result = ComparePreRelease(x, y); - } - } - } - return result; - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; + +namespace Microsoft.NodejsTools.Npm { + + /// + /// Performs semver version comparison in accordance with the rules of precedence + /// set out under point 11 at http://semver.org/. + /// + public class SemverVersionComparer : IComparer { + private bool IsNumericIdentifier(string identifier) { + for (int index = 0, size = identifier.Length; index < size; ++index) { + // If any non-digit is detected, or the first character is a leading 0, + // the identifier is treated as non-numeric + if (!char.IsDigit(identifier[index]) + || index == 0 && identifier[index] == '0') { + return false; + } + } + return true; + } + + private int ComparePreRelease(SemverVersion x, SemverVersion y) { + string xp = x.PreReleaseVersion, + yp = y.PreReleaseVersion; + + // Empty pre-release version has higher precedence than a value for pre-release version + if (string.IsNullOrEmpty(xp)) { + return string.IsNullOrEmpty(yp) ? 0 : 1; + } + + // Same as above; + if (string.IsNullOrEmpty(yp)) { + return -1; + } + + // Identifiers are separated by dots + string[] xs = xp.Split('.'), + ys = yp.Split('.'); + + // Compare identifiers individually until a difference is found + for (int index = 0, size = Math.Min(xs.Length, ys.Length); index < size; ++index) { + // Figure out which items in each pair of identifiers is numeric - has a profound + // impace on the subsequent comparison performed. + bool xn = IsNumericIdentifier(xs[index]), + yn = IsNumericIdentifier(ys[index]); + if (xn) { + if (yn) { + // Compare numeric identifiers in the expected way + var result = int.Parse(xs[index]).CompareTo(int.Parse(ys[index])); + if (0 != result) { + return result; + } + } else { + // Numeric identifiers have lower precedence than non-numeric, so y is greater + return -1; + } + } else if (yn) { + // Numeric identifiers have lower precedence than non-numeric, so x is greater + return 1; + } else { + var result = string.Compare(xs[index], ys[index], StringComparison.CurrentCulture); + if (0 != result) { + return result; + } + } + } + + // Still the same? More fields in pre-release information indicates higher precedence, + // otherwise identifiers are the same. + + if (xs.Length == ys.Length) { + return 0; + } + + if (xs.Length < ys.Length) { + return -1; + } + + return 1; + } + + public int Compare(SemverVersion x, SemverVersion y) { + // The version number comparisons are straightforward until + // we get into comparing pre-release information. In most cases + // this shouldn't be necessary. Note that build metadata is + // NOT included in any precedence comparison, and versions that + // differ only in build metadata should be treated as equivalent. + var result = x.Major.CompareTo(y.Major); + if (0 == result) { + result = x.Minor.CompareTo(y.Minor); + if (0 == result) { + result = x.Patch.CompareTo(y.Patch); + if (0 == result) { + result = ComparePreRelease(x, y); + } + } + } + return result; + } + } +} diff --git a/Nodejs/Product/Npm/SemverVersionFormatException.cs b/Nodejs/Product/Npm/SemverVersionFormatException.cs index 5e6721c09..be9cd06c7 100644 --- a/Nodejs/Product/Npm/SemverVersionFormatException.cs +++ b/Nodejs/Product/Npm/SemverVersionFormatException.cs @@ -1,37 +1,37 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Runtime.Serialization; - -namespace Microsoft.NodejsTools.Npm { - [Serializable] - public class SemverVersionFormatException : FormatException { - // I created this class mainly for the purposes of testability. Semver parsing might fail for any - // number of reasons with a format exception, which is what I originally used, but since that may - // also be thrown by methods called by SemverVersion.Parse, tests can't differentiate correct handling - // of bad input versus behaviour that might be a bug. - - public SemverVersionFormatException() { } - - public SemverVersionFormatException(string message) : base(message) { } - - public SemverVersionFormatException(string message, Exception innerException) : base(message, innerException) { } - - protected SemverVersionFormatException(SerializationInfo info, StreamingContext context) : base(info, context) { } - - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Runtime.Serialization; + +namespace Microsoft.NodejsTools.Npm { + [Serializable] + public class SemverVersionFormatException : FormatException { + // I created this class mainly for the purposes of testability. Semver parsing might fail for any + // number of reasons with a format exception, which is what I originally used, but since that may + // also be thrown by methods called by SemverVersion.Parse, tests can't differentiate correct handling + // of bad input versus behaviour that might be a bug. + + public SemverVersionFormatException() { } + + public SemverVersionFormatException(string message) : base(message) { } + + public SemverVersionFormatException(string message, Exception innerException) : base(message, innerException) { } + + protected SemverVersionFormatException(SerializationInfo info, StreamingContext context) : base(info, context) { } + + } } \ No newline at end of file diff --git a/Nodejs/Product/ProjectWizard/DockerfileWizardExtension.cs b/Nodejs/Product/ProjectWizard/DockerfileWizardExtension.cs index 62dee8f88..0eb18c366 100644 --- a/Nodejs/Product/ProjectWizard/DockerfileWizardExtension.cs +++ b/Nodejs/Product/ProjectWizard/DockerfileWizardExtension.cs @@ -1,79 +1,79 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using EnvDTE; -using Microsoft.VisualStudio.TemplateWizard; -using Microsoft.VisualStudioTools; - -namespace Microsoft.NodejsTools.ProjectWizard { - /// - /// Provides a project wizard extension which will replace the node arguments - /// in a Dockerfile with properties from the project file. - /// - public sealed class DockerfileWizardExtension : IWizard { - public void BeforeOpeningFile(ProjectItem projectItem) { - return; - } - - public void ProjectFinishedGenerating(EnvDTE.Project project) { - return; - } - - public void ProjectItemFinishedGenerating(ProjectItem projectItem) { - return; - } - - public void RunFinished() { - return; - } - - public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) { - string arguments = GetFullNodeArguments(automationObject); - replacementsDictionary.Add("$nodearguments$", arguments); - } - - private static string GetFullNodeArguments(object automationObject) { - string arguments = null; - if (automationObject is DTE) { - DTE dte = (DTE)automationObject; - Array activeProjects = (Array)dte.ActiveSolutionProjects; - - if (activeProjects.Length > 0) { - EnvDTE.Project activeProject = (EnvDTE.Project)activeProjects.GetValue(0); - string startupFileName = CommonUtils.GetRelativeFilePath( - (string)activeProject.Properties.Item("ProjectHome").Value, - (string)activeProject.Properties.Item("StartupFile").Value ?? string.Empty).Replace("\\", "/"); - - string nodeExeArguments = (string)activeProject.Properties.Item("NodeExeArguments").Value ?? string.Empty; - string scriptArguments = (string)activeProject.Properties.Item("ScriptArguments").Value ?? string.Empty; - - arguments = String.Format("{0} {1} {2}", - nodeExeArguments.Trim(), - startupFileName.Trim(), - scriptArguments.Trim() - ).Trim(); - } - } - return arguments; - } - - public bool ShouldAddProjectItem(string filePath) { - return true; - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using EnvDTE; +using Microsoft.VisualStudio.TemplateWizard; +using Microsoft.VisualStudioTools; + +namespace Microsoft.NodejsTools.ProjectWizard { + /// + /// Provides a project wizard extension which will replace the node arguments + /// in a Dockerfile with properties from the project file. + /// + public sealed class DockerfileWizardExtension : IWizard { + public void BeforeOpeningFile(ProjectItem projectItem) { + return; + } + + public void ProjectFinishedGenerating(EnvDTE.Project project) { + return; + } + + public void ProjectItemFinishedGenerating(ProjectItem projectItem) { + return; + } + + public void RunFinished() { + return; + } + + public void RunStarted(object automationObject, Dictionary replacementsDictionary, WizardRunKind runKind, object[] customParams) { + string arguments = GetFullNodeArguments(automationObject); + replacementsDictionary.Add("$nodearguments$", arguments); + } + + private static string GetFullNodeArguments(object automationObject) { + string arguments = null; + if (automationObject is DTE) { + DTE dte = (DTE)automationObject; + Array activeProjects = (Array)dte.ActiveSolutionProjects; + + if (activeProjects.Length > 0) { + EnvDTE.Project activeProject = (EnvDTE.Project)activeProjects.GetValue(0); + string startupFileName = CommonUtils.GetRelativeFilePath( + (string)activeProject.Properties.Item("ProjectHome").Value, + (string)activeProject.Properties.Item("StartupFile").Value ?? string.Empty).Replace("\\", "/"); + + string nodeExeArguments = (string)activeProject.Properties.Item("NodeExeArguments").Value ?? string.Empty; + string scriptArguments = (string)activeProject.Properties.Item("ScriptArguments").Value ?? string.Empty; + + arguments = String.Format("{0} {1} {2}", + nodeExeArguments.Trim(), + startupFileName.Trim(), + scriptArguments.Trim() + ).Trim(); + } + } + return arguments; + } + + public bool ShouldAddProjectItem(string filePath) { + return true; + } + } +} diff --git a/Nodejs/Product/ProjectWizard/ImportWizard.cs b/Nodejs/Product/ProjectWizard/ImportWizard.cs index 64b24ede0..80956791d 100644 --- a/Nodejs/Product/ProjectWizard/ImportWizard.cs +++ b/Nodejs/Product/ProjectWizard/ImportWizard.cs @@ -22,8 +22,8 @@ using Microsoft.VisualStudio; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TemplateWizard; - +using Microsoft.VisualStudio.TemplateWizard; + namespace Microsoft.NodejsTools.ProjectWizard { public sealed class NewProjectFromExistingWizard : IWizard { public static Boolean IsAddNewProjectCmd { get; set; } diff --git a/Nodejs/Product/ProjectWizard/NpmWizardExtension.cs b/Nodejs/Product/ProjectWizard/NpmWizardExtension.cs index 59c650908..705655dbc 100644 --- a/Nodejs/Product/ProjectWizard/NpmWizardExtension.cs +++ b/Nodejs/Product/ProjectWizard/NpmWizardExtension.cs @@ -31,7 +31,7 @@ public void BeforeOpeningFile(EnvDTE.ProjectItem projectItem) { public void ProjectFinishedGenerating(EnvDTE.Project project) { Debug.Assert(project != null && project.Object != null); - Debug.Assert(project.Object is INodePackageModulesCommands); + Debug.Assert(project.Object is INodePackageModulesCommands); ((INodePackageModulesCommands)project.Object).InstallMissingModulesAsync(); } diff --git a/Nodejs/Product/TestAdapter/TestDiscoverer.cs b/Nodejs/Product/TestAdapter/TestDiscoverer.cs index e8f53acc7..62baa41b5 100644 --- a/Nodejs/Product/TestAdapter/TestDiscoverer.cs +++ b/Nodejs/Product/TestAdapter/TestDiscoverer.cs @@ -102,8 +102,8 @@ private void DiscoverTests(Dictionary> testItems, MS var projectHome = Path.GetFullPath(Path.Combine(proj.DirectoryPath, ".")); var projSource = ((MSBuild.Project)proj).FullPath; - var nodeExePath = - Nodejs.GetAbsoluteNodeExePath( + var nodeExePath = + Nodejs.GetAbsoluteNodeExePath( projectHome, proj.GetPropertyValue(NodejsConstants.NodeExePath)); diff --git a/Nodejs/Product/TestAdapter/TestExecutor.cs b/Nodejs/Product/TestAdapter/TestExecutor.cs index 5d4792617..c1a36d234 100644 --- a/Nodejs/Product/TestAdapter/TestExecutor.cs +++ b/Nodejs/Product/TestAdapter/TestExecutor.cs @@ -239,8 +239,8 @@ private NodejsProjectSettings LoadProjectSettings(string projectFile) { projSettings.WorkingDir = Path.GetFullPath(Path.Combine(projectRootDir, proj.GetPropertyValue(CommonConstants.WorkingDirectory) ?? ".")); - projSettings.NodeExePath = - Nodejs.GetAbsoluteNodeExePath( + projSettings.NodeExePath = + Nodejs.GetAbsoluteNodeExePath( projectRootDir, proj.GetPropertyValue(NodejsConstants.NodeExePath)); diff --git a/Nodejs/Product/TestAdapter/TestFrameworks/TestFramework.cs b/Nodejs/Product/TestAdapter/TestFrameworks/TestFramework.cs index d9002d38a..6b9c663a8 100644 --- a/Nodejs/Product/TestAdapter/TestFrameworks/TestFramework.cs +++ b/Nodejs/Product/TestAdapter/TestFrameworks/TestFramework.cs @@ -111,10 +111,10 @@ private string WrapWithQuotes(string path) { return path; } - /// - /// Wrap name of the test in the quotes, to be passed to the command line. - /// - /// Name of the test to excape for command line usage. + /// + /// Wrap name of the test in the quotes, to be passed to the command line. + /// + /// Name of the test to excape for command line usage. /// Name of the test, escaped according to the command line rules. private string WrapTestNameWithQuotes(string testName) { return "\"" + testName.Replace("\"", "\\\"") + "\""; diff --git a/Nodejs/Tests/AnalysisTests/FormatterTests.cs b/Nodejs/Tests/AnalysisTests/FormatterTests.cs index 8cbf142d2..c7a154861 100644 --- a/Nodejs/Tests/AnalysisTests/FormatterTests.cs +++ b/Nodejs/Tests/AnalysisTests/FormatterTests.cs @@ -1,2329 +1,2329 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.IO; -using System.Text; -using Microsoft.NodejsTools.Formatting; -using Microsoft.NodejsTools.Parsing; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TestUtilities; - -namespace AnalysisTests { - [TestClass] - public class FormatterTests { -#if FALSE - [TestMethod, Priority(0)] - public void ReformatDirectory() { - ReformatDirectory("C:\\Source\\Express\\node_modules\\", "C:\\Source\\Express\\formatted\\"); - } - - private void ReformatDirectory(string inp, string output) { - FormattingOptions options = new FormattingOptions() { SpacesPerIndent = 2, SpaceAfterFunctionInAnonymousFunctions = false }; - foreach (var file in Directory.GetFiles(inp, "*.js", SearchOption.AllDirectories)) { - var newCode = FormatCode(File.ReadAllText(file), options); - var outFile = Path.Combine(output, file.Substring(inp.Length)); - Directory.CreateDirectory(Path.GetDirectoryName(outFile)); - - File.WriteAllText(outFile, newCode); - } - } -#endif - - [ClassInitialize] - public static void ClassInititalize(TestContext context) { - AssertListener.Initialize(); - } - - [TestMethod, Priority(0)] - public void TestAnonymousFunction() { - TestCode( -@"a('hello', 15, function(err, res) { -console.log(err); - b(res, 55, function(err, res2) { -console.log(err); - }); -});", - @"a('hello', 15, function (err, res) { - console.log(err); - b(res, 55, function (err, res2) { - console.log(err); - }); -});", new FormattingOptions() { SpacesPerIndent = 2 }); - - } - - /// - /// https://nodejstools.codeplex.com/workitem/1351 - /// - [TestMethod, Priority(0)] - public void TestNestedFunctionAndArrayLiteral() { - TestCode( -@"var a = require('a'); -a.mount('/', [ -pipe.static({ root: 'b' }), -pipe.proxy({ -'a': 1 -}) -]);", - @"var a = require('a'); -a.mount('/', [ - pipe.static({ root: 'b' }), - pipe.proxy({ - 'a': 1 - }) -]);"); - - TestCode( -@"switch (abc) { - case foo: function a() { - x = 42; y = 100;[1, - 2, 3] - } -}", - @"switch (abc) { - case foo: function a() { - x = 42; y = 100; [1, - 2, 3] - } -}"); - } - - [TestMethod, Priority(0)] - public void TestJsonArray() { - TestCode( -@"(function (seedData) { - seedData.initialNotes = [{ - name: ""category"", - notes: [{ - note: ""note1"", - author: ""author1"", - color: ""blue"" - }, - { - note: ""note1"", - author: ""author2"", - color: ""green"" - }] - }]; -})(module.exports);", -@"(function (seedData) { - seedData.initialNotes = [{ - name: ""category"", - notes: [{ - note: ""note1"", - author: ""author1"", - color: ""blue"" - }, - { - note: ""note1"", - author: ""author2"", - color: ""green"" - }] - }]; -})(module.exports);" - - ); - } - - [TestMethod, Priority(0)] - public void TestFormatAfterInvalidKey() { - Assert.AreEqual(0, Formatter.GetEditsAfterKeystroke("function f () { }", 0, ':').Length); - } - - /// - /// - /// - [TestMethod, Priority(0)] - public void TestSemicolonEndOfDocument() { - var testCode = new[] { - // https://nodejstools.codeplex.com/workitem/1102 - new { Before = "x=1\r\nconsole.log( 'hi');", After = "x=1\r\nconsole.log('hi');" }, - // https://nodejstools.codeplex.com/workitem/1075 - new { Before = "function hello () {\r\n return;", After = "function hello() {\r\n return;" } - }; - - foreach (var test in testCode) { - var code = test.Before; - Console.WriteLine(code); - - TestCode(code.IndexOf(';') + 1, ';', code, test.After); - } - } - - [TestMethod, Priority(0)] - public void TestFormatAfterEnter() { - var testCode = new[] { - // https://nodejstools.codeplex.com/workitem/1078 - new { Before = "function f() {\r\n if(true)!\r\n}", After = "function f() {\r\n if (true)\r\n\r\n}" }, - // https://nodejstools.codeplex.com/workitem/1075 - new { Before = "function hello() {!", After = "function hello() {\r\n" }, - // https://nodejstools.codeplex.com/workitem/1136 - new { Before = ";\r\n{\r\n /*!\r\n}", After = ";\r\n{\r\n /*\r\n\r\n}" }, - // https://nodejstools.codeplex.com/workitem/1133 - new { Before = ";\r\nvar mailOptions = {\r\n from: 'blahblah',!", After = ";\r\nvar mailOptions = {\r\n from: 'blahblah',\r\n" }, - // https://nodejstools.codeplex.com/workitem/1175 - new { Before = ";\r\nif(true)\r\n if (true)!", After = ";\r\nif (true)\r\n if (true)\r\n" }, - }; - - foreach (var test in testCode) { - Console.WriteLine(test.Before); - string code = test.Before.Replace("!", "\r\n"); - int enterIndex = code.IndexOf(';') + 2; - var lines = code.Split(new[] { "\r\n" }, StringSplitOptions.None); - int startIndex = 0, endIndex = lines[0].Length + lines[1].Length + ("\r\n".Length * 2); - for (int i = 1; i < lines.Length; i++) { - if (enterIndex < endIndex) { - break; - } - - startIndex += lines[i - 1].Length + "\r\n".Length; - endIndex += lines[i].Length + "\r\n".Length; - } - - TestEnter(startIndex, endIndex, code, test.After); - } - } - - [TestMethod, Priority(0)] - public void TestFormatAfterSemiColon() { - string surroundingCode = "x=1"; - var testCode = new[] { - new { Before = "function f() { \r\n return 42;\r\n}", After = "function f() { \r\n return 42;\r\n}" }, - new { Before = "function f() { \r\n return 42 ;\r\n}", After = "function f() { \r\n return 42;\r\n}" }, - new { Before = "function f() { \r\n return ;\r\n}", After = "function f() { \r\n return;\r\n}" }, - new { Before = "throw 42;", After = "throw 42;" }, - new { Before = "throw ;", After = "throw;" }, - new { Before = "x=42;", After = "x = 42;" }, - new { Before = "x=42;", After = "x = 42;" }, - new { Before = "debugger ;", After = "debugger;" }, - new { Before = "while (true) { \r\n break ;\r\n}", After = "while (true) { \r\n break;\r\n}" }, - new { Before = "while (true) { \r\n continue ;\r\n}", After = "while (true) { \r\n continue;\r\n}" }, - new { Before = "var x=1,y=2;", After = "var x = 1, y = 2;" }, - // https://nodejstools.codeplex.com/workitem/1346 - new { Before = "while(true){\r\nconsole.log('hello';)\r\n}", After = "while(true){\r\nconsole.log('hello';)\r\n}"} - }; - - foreach (var test in testCode) { - Console.WriteLine(test.Before); - string code = surroundingCode + "\r\n" + test.Before + "\r\n" + surroundingCode; - string expected = surroundingCode + "\r\n" + test.After + "\r\n" + surroundingCode; - TestCode( - code.IndexOf(';') + 1, - ';', - code, - expected - ); - } - } - - [TestMethod, Priority(0)] - public void TestSemicolonOnOwnLine() { - // https://nodejstools.codeplex.com/workitem/1473 - TestCode( - @"function f() { - console.log('hi') -; -}", - @"function f() { - console.log('hi') - ; -}"); - - TestCode( - @"function f() { - console.log('hi') // comment - ; -}", - @"function f() { - console.log('hi') // comment - ; -}"); - } - - [TestMethod, Priority(0)] - public void TestFormattingFunctionAsArgumentToFunction() { - // Format dedenting first argument too much - // https://nodejstools.codeplex.com/workitem/1463 - TestCode( - @"g( - function f(a, b, c) - { - console.log('hi'); - console.log('bar') - } -)", - @"g( - function f(a, b, c) { - console.log('hi'); - console.log('bar') - } -)" - ); - // Format indenting second argument function too much - // https://nodejstools.codeplex.com/workitem/1459 - TestCode( - @"g(function () { - console.log('hi') -}, function () { - console.log('toofar') - });", - @"g(function () { - console.log('hi') -}, function () { - console.log('toofar') -});"); - - TestCode( -@"g( - function () { - console.log('hi') - }, function () { - console.log('hi2') - });", -@"g( - function () { - console.log('hi') - }, function () { - console.log('hi2') - });"); - } - - [TestMethod, Priority(0)] - public void TestArrayLiteral() { - var testCode = new[] { - // https://nodejstools.codeplex.com/workitem/1474 - new { Before = "function f() {\r\n console.log('hi')\r\n x = [1,2,3]\r\n}", - After = "function f() {\r\n console.log('hi')\r\n x = [1, 2, 3]\r\n}"}, - new { Before = "function f() {\r\n console.log('hi')\r\n [1,2,3]\r\n}", - After = "function f() {\r\n console.log('hi')\r\n [1, 2, 3]\r\n}"}, - new { Before = "g(\r\n function f(a, b, c)\r\n {\r\n console.log('hi');\r\n [1,2,3]\r\n }\r\n)", - After = "g(\r\n function f(a, b, c) {\r\n console.log('hi');\r\n [1, 2, 3]\r\n }\r\n)"}, - new { Before = "var x =[\r\n1, 2, 3\r\n ]", - After = "var x = [\r\n 1, 2, 3\r\n]"}, - // Test multiple lines stay aligned, but individual single line arrays fixed - new { Before = "function f() {\r\n var x = [[1],\r\n [2, 3],\r\n [3,4,5]]\r\n}", - After = "function f() {\r\n var x = [[1],\r\n [2, 3],\r\n [3, 4, 5]]\r\n}"}, - // https://nodejstools.codeplex.com/workitem/1494 We shouldn't push the 3 & 4 together - new { Before = "var x = [1,2,3 4]", - After = "var x = [1, 2, 3 4]"}, - }; - - foreach (var test in testCode) { - TestCode(test.Before, test.After); - } - } - - [TestMethod, Priority(0)] - public void TestFormatAfterBadFor() { - TestCode(@"function g() { - for(int i = 0; i<1000000; i++) { - }", - @"function g() { - for (int i = 0; i < 1000000; i++) { - }"); - - // https://nodejstools.codeplex.com/workitem/1475 - TestCode(@"for { - x = 2}", - @"for { - x = 2}"); - } - - [TestMethod, Priority(0)] - public void TestIndexingOnFollowingLine() { - // https://nodejstools.codeplex.com/workitem/1465 - TestCode( - @"g() -[]", - @"g() -[]"); - } - - /// - /// https://nodejstools.codeplex.com/workitem/1204 - /// - [TestMethod, Priority(0)] - public void TestShebang() { - TestCode("#!/usr/env/node\r\n function f() {\r\n}", "#!/usr/env/node\r\nfunction f() {\r\n}"); - } - - [TestMethod, Priority(0)] - public void TestInvalidTrailingQuote() { - TestCode("var x = foo()'", "var x = foo() '"); - TestCode("return 42'", "return 42 '"); - TestCode("continue'", "continue '"); - - TestCode("break'", "break '"); - TestCode("throw 42'", "throw 42 '"); - } - - [TestMethod, Priority(0)] - public void TestMember() { - TestCode(" a.b", "a.b"); - TestCode("a .b", "a.b"); - TestCode("a. b", "a.b"); - TestCode(" a . b", "a.b"); - } - - [TestMethod, Priority(0)] - public void TestInvalidMember() { - TestCode("x.42", "x.42"); - TestCode("x3.42", "x3.42"); - TestCode("x_.23", "x_.23"); - TestCode("x23.42", "x23.42"); - TestCode("x23.42m", "x23.42m"); - TestCode("x23.\"hello\"", "x23.\"hello\""); - } - - [TestMethod, Priority(0)] - public void TestCall() { - TestCode("a()", "a()"); - TestCode("a ()", "a()"); - TestCode("a( b)", "a(b)"); - TestCode("a(b )", "a(b)"); - TestCode("a( b )", "a(b)"); - TestCode("a(b,c)", "a(b, c)"); - TestCode("a(b, c)", "a(b, c)"); - TestCode("a(b, c )", "a(b, c)"); - //https://nodejstools.codeplex.com/workitem/1525 - TestCode( -@"socket.on(""disconnect"", function () { - var a = 2; -}).on('message', function () { - var b = 1; -});", -@"socket.on(""disconnect"", function () { - var a = 2; -}).on('message', function () { - var b = 1; -});"); - } - - [TestMethod, Priority(0)] - public void TestUnaryOperator() { - TestCode("typeof x", "typeof x"); - TestCode("delete x", "delete x"); - TestCode("void x", "void x"); - TestCode("++ x", "++x"); - } - - [TestMethod, Priority(0)] - public void TestEmpty() { - TestCode("if (true);", "if (true);"); - TestCode("if (true) ;", "if (true);"); - - // https://github.com/Microsoft/nodejstools/issues/24 - TestCode( -@"var x = function () { - if (a) { - 123455 - }; -};", -@"var x = function () { - if (a) { - 123455 - }; -};"); - - // Shouldn't insert additional space between semicolons. - TestCode( -@"function a() { - var a;; -}", -@"function a() { - var a;; -}"); - } - - [TestMethod, Priority(0)] - public void TestNew() { - TestCode("var x = new Blah();", "var x = new Blah();"); - - } - - [TestMethod, Priority(0)] - public void TestFormatRange() { - string surroundingCode = "x=1"; - var testCode = new[] { - new { Before = "function f() { \r\n return 42;\r\n}", After = "function f() {\r\n return 42;\r\n}" }, - new { Before = "throw 42;", After = "throw 42;" }, - new { Before = "throw ;", After = "throw;" }, - new { Before = "x=42;", After = "x = 42;" }, - new { Before = "x=42;", After = "x = 42;" }, - new { Before = "debugger ;", After = "debugger;" }, - new { Before = "while (true) { \r\n break ;\r\n}", After = "while (true) {\r\n break;\r\n}" }, - new { Before = "while (true) { \r\n continue ;\r\n}", After = "while (true) {\r\n continue;\r\n}" }, - new { Before = "var x=1,y=2;", After = "var x = 1, y = 2;" }, - new { Before = "var x=1,y=2 ;", After = "var x = 1, y = 2;" }, - }; - - foreach (var test in testCode) { - Console.WriteLine(test.Before); - string code = surroundingCode + "\r\n" + test.Before + "\r\n" + surroundingCode; - string expected = surroundingCode + "\r\n" + test.After + "\r\n" + surroundingCode; - - // also check range - TestCode( - surroundingCode.Length, - code.Length - surroundingCode.Length, - code, - expected - ); - } - } - - [TestMethod, Priority(0)] - public void TestFormatAfterCloseBrace() { - string surroundingCode = "x=1"; - var testCode = new[] { - new { Before = "while(true) {\r\nblah\r\n!", After = "while (true) {\r\n blah\r\n}" }, - new { Before = "with(true) {\r\nblah\r\n!", After = "with (true) {\r\n blah\r\n}" }, - new { Before = "for(var i=0;i<10;i++) {\r\nblah\r\n!", After = "for (var i = 0; i < 10; i++) {\r\n blah\r\n}" }, - new { Before = "for(var x in []) {\r\nblah\r\n!", After = "for (var x in []) {\r\n blah\r\n}" }, - new { Before = "{\r\nblah\r\n!", After = "{\r\n blah\r\n}" }, - new { Before = "switch(abc){\r\ncase 42: return null;\r\n!", After = "switch (abc) {\r\n case 42: return null;\r\n}" }, - new { Before = "try {\r\nabc\r\n!", After = "try {\r\n abc\r\n}" }, - new { Before = "try {\r\nabc\r\n}catch(abc){\r\nabc\r\n!", After = "try {\r\n abc\r\n} catch (abc) {\r\n abc\r\n}" }, - new { Before = "try {\r\nabc\r\n}finally{\r\nabc\r\n!", After = "try {\r\n abc\r\n} finally {\r\n abc\r\n}" }, - new { Before = "{\r\n break;\r\n!", After = "{\r\n break;\r\n}" }, - new { Before = "{\r\n break ;\r\n!", After = "{\r\n break;\r\n}" }, - // https://nodejstools.codeplex.com/workitem/1346 - new { Before = "module.exports = {\r\n f: function () { console!\r\n}", After = "module.exports = {\r\n f: function () { console }\r\n}" }, - }; - - foreach (var test in testCode) { - Console.WriteLine(test.Before); - string indexCode = surroundingCode + "\r\n" + test.Before + "\r\n" + surroundingCode; - string code = surroundingCode + "\r\n" + test.Before.Replace('!', '}') + "\r\n" + surroundingCode; - string expected = surroundingCode + "\r\n" + test.After + "\r\n" + surroundingCode; - TestCode( - indexCode.IndexOf('!') + 1, - '}', - code, - expected - ); - } - } - - [TestMethod, Priority(0)] - public void TestLabeledStatement() { - TestCode(@"foo: { - 42; -}", - @"foo: { - 42; -}"); - - } - - [TestMethod, Priority(0)] - public void TestContinue() { - TestCode( -@"while (true) { - continue -}", -@"while (true) { - continue -}" -); - - TestCode( -@"while (true) { - continue -}", -@"while (true) { - continue -}" -); - TestCode( -@"while (true) { -label: - while (true) { - continue label - } -}", -@"while (true) { - label: - while (true) { - continue label - } -}" -); - } - - [TestMethod, Priority(0)] - public void TestBlock() { - TestCode( - "{\nvar b;\n}", - "{\n var b;\n}", - new FormattingOptions() { - NewLine = "\n" - }); - - TestCode( - "{\rvar b;\r}", - "{\r var b;\r}", - new FormattingOptions() { - NewLine = "\r" - } - ); - - TestCode( - "{\r\nvar b;\r\n}", - "{\r\n var b;\r\n}" - ); - } - - [TestMethod, Priority(0)] - public void TestBreak() { - TestCode( -@"while (true) { - break -}", -@"while (true) { - break -}" -); - - TestCode( -@"while (true) { - break -}", -@"while (true) { - break -}" -); - TestCode( -@"while (true) { - break}", -@"while (true) { - break -}" -); - - TestCode( -@"while (true) { -label: - while (true) { - break label - } -}", -@"while (true) { - label: - while (true) { - break label - } -}" -); - } - - [TestMethod, Priority(0)] - public void TestFunction() { - TestCode( -@"function f () { -}", -@"function f() { -}"); - // https://nodejstools.codeplex.com/workitem/1740 - TestCode( -@"exports.hugues = function(req,res){ res.render('hugues', { title: 'Hugues', year: new Date().getFullYear(), message: 'Your hugues page.' }); -};", -@"exports.hugues = function (req, res) { - res.render('hugues', { title: 'Hugues', year: new Date().getFullYear(), message: 'Your hugues page.' }); -};"); - } - - [TestMethod, Priority(0)] - public void TestReturn() { - TestCode( -@"function f() { - return -}", -@"function f() { - return -}" -); - - TestCode( -@"function f() { - return -}", -@"function f() { - return -}" -); - TestCode( -@"function f() { - return 42 -}", -@"function f() { - return 42 -}" -); - - TestCode( -@"function f() { - return 42; -}", -@"function f() { - return 42; -}" -); - } - - [TestMethod, Priority(0)] - public void TestYield() { - TestCode( -@"function *f() { - yield -}", -@"function* f() { - yield -}" -); - - TestCode( -@"function *f() { - yield -}", -@"function* f() { - yield -}" -); - TestCode( -@"function *f() { - yield 42 -}", -@"function* f() { - yield 42 -}" -); - - TestCode( -@"function *f() { - yield 42; -}", -@"function* f() { - yield 42; -}" -); - - TestCode( -@"function * f() { - yield 42; -}", -@"function* f() { - yield 42; -}" -); - - TestCode( -@"function * f() { - yield * 42; -}", -@"function* f() { - yield* 42; -}" -); - } - - [TestMethod, Priority(0)] - public void TestThrow() { - TestCode( -@"function f() { - throw -}", -@"function f() { - throw -}" -); - - TestCode( -@"function f() { - throw -}", -@"function f() { - throw -}" -); - TestCode( -@"function f() { - throw 42 -}", -@"function f() { - throw 42 -}" -); - - TestCode( -@"function f() { - throw 42; -}", -@"function f() { - throw 42; -}" -); - } - - [TestMethod, Priority(0)] - public void TestObjectLiteral() { - TestCode( -@"x = { get foo() { }, set foo(value) { } }", -@"x = { get foo() { }, set foo(value) { } }" -); - - - TestCode( -@"x = { }", -@"x = {}" -); - - TestCode( -@"x = { -}", -@"x = { -}" -); - - TestCode( -@"x = { -a: 42, -b: 100}", -@"x = { - a: 42, - b: 100 -}" - ); - - TestCode( -@"x = { -a: 42, b: 100, -c: 42, d: 100}", -@"x = { - a: 42, b: 100, - c: 42, d: 100 -}" - ); - TestCode( -@"x = {a:42, b:100}", -@"x = { a: 42, b: 100 }" - ); - - //https://nodejstools.codeplex.com/workitem/1525 - TestCode( -@"var a = function (test) { - return { - } -}", -@"var a = function (test) { - return { - } -}" -); - - //https://nodejstools.codeplex.com/workitem/1560 - TestCode( -@"var a = function (test) { - return { - - } -}", -@"var a = function (test) { - return { - - } -}" -); - } - - [TestMethod, Priority(0)] - public void TestIndentObjectLiteralWithoutComma() { - // https://nodejstools.codeplex.com/workitem/1782 - TestCode(@"Main.Test.prototype = { - testFunc: function () { - - }, - testFunc3: function () {} - testFunc2: function () { - - } -}", -@"Main.Test.prototype = { - testFunc: function () { - - }, - testFunc3: function () { } - testFunc2: function () { - - } -}"); - - TestCode(@"Main.Test.prototype = { - testFunc: function () { - - }, - testFunc3: function () { -} - testFunc2: function () { - - } -}", -@"Main.Test.prototype = { - testFunc: function () { - - }, - testFunc3: function () { - } - testFunc2: function () { - - } -}"); - } - - [TestMethod, Priority(0)] - public void TestDoWhile() { - TestCode(@"do - { var a -} while (1)", - @"do { - var a -} while (1)"); - - - } - - [TestMethod, Priority(0)] - public void TestControlFlowBraceCombo() { - var options = new FormattingOptions() { OpenBracesOnNewLineForControl = false, SpaceAfterKeywordsInControlFlowStatements = false }; - - TestCode( -@"do { -} while (true);", -@"do { -} while (true);", - options - ); - - TestCode( -@"try { -} finally { -}", -@"try { -} finally { -}", - options - ); - } - - [TestMethod, Priority(0)] - public void TestSpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis() { - var options = new FormattingOptions() { SpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis = true }; - - TestCode( -@"for (var x in abc) { -}", -@"for ( var x in abc ) { -}", - options - ); - - - TestCode( -@"for (var i = 0; i < 10; i++) { -}", -@"for ( var i = 0; i < 10; i++ ) { -}", - options - ); - - TestCode( -@"if (true) { -}", -@"if ( true ) { -}", - options - ); - - TestCode( -@"while (true) { -}", -@"while ( true ) { -}", - options - ); - - TestCode( -@"do { -} while (true);", -@"do { -} while ( true );", - options - ); - - TestCode( -@"try { -} catch (foo) { -}", -@"try { -} catch ( foo ) { -}", - options - ); - - TestCode( -@"function () { -}", -@"function () { -}", - options - ); - - TestCode( -@"function ( ) { -}", -@"function () { -}", - options - ); - - TestCode( -@"function (a) { -}", -@"function ( a ) { -}", - options - ); - - TestCode( -@"function (a, b) { -}", -@"function ( a, b ) { -}", - options - ); - - TestCode( -@"(a)", -@"( a )", - options - ); - - TestCode( -@"f(a)", -@"f( a )", - options - ); - - TestCode( -@"f(a, b)", -@"f( a, b )", - options - ); - - TestCode( -@"new f(a)", -@"new f( a )", - options - ); - - TestCode( -@"new f(a, b)", -@"new f( a, b )", - options - ); - - TestCode( -@"f[a]", -@"f[a]", - options - ); - - TestCode( -@"f[a, b]", -@"f[a, b]", - options - ); - - TestCode( -@"switch (abc) { - case 42: break; -}", -@"switch ( abc ) { - case 42: break; -}", - options - ); - - TestCode( -@"(x)", -@"( x )", - options - ); - } - - [TestMethod, Priority(0)] - public void TestSpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis2() { - var options = new FormattingOptions() { SpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis = false }; - - TestCode( -@"for ( var x in abc ) { -}", -@"for (var x in abc) { -}", - options - ); - - - TestCode( -@"for ( var i = 0; i < 10; i++ ) { -}", -@"for (var i = 0; i < 10; i++) { -}", - options - ); - - TestCode( -@"if ( true ) { -}", -@"if (true) { -}", - options - ); - - TestCode( -@"while ( true ) { -}", -@"while (true) { -}", - options - ); - - TestCode( -@"do { -} while ( true );", -@"do { -} while (true);", - options - ); - - TestCode( -@"try { -} catch ( foo ) { -}", -@"try { -} catch (foo) { -}", - options - ); - - TestCode( -@"function () { -}", -@"function () { -}", - options - ); - - - TestCode( -@"function ( a ) { -}", -@"function (a) { -}", - options - ); - - TestCode( -@"function ( a, b ) { -}", -@"function (a, b) { -}", - options - ); - - TestCode( -@"( a )", -@"(a)", - options - ); - - TestCode( -@"f( a )", -@"f(a)", - options - ); - - TestCode( -@"f( a, b )", -@"f(a, b)", - options - ); - - TestCode( -@"new f( a )", -@"new f(a)", - options - ); - - TestCode( -@"new f( a, b )", -@"new f(a, b)", - options - ); - - TestCode( -@"f[a]", -@"f[a]", - options - ); - - TestCode( -@"f[a, b]", -@"f[a, b]", - options - ); - - TestCode( -@"switch ( abc ) { - case 42: break; -}", -@"switch (abc) { - case 42: break; -}", - options - ); - - TestCode( -@"( x )", -@"(x)", - options - ); - } - - [TestMethod, Priority(0)] - public void TestArithmetic() { - TestCode("a+.0", "a + .0"); - } - - [TestMethod, Priority(0)] - public void TestVariableDecl() { - TestCode(@"var i = 0, j = 1;", @"var i = 0, j = 1;"); - TestCode(@"var i=0, j=1;", @"var i = 0, j = 1;"); - TestCode(@"var i = 0 , j = 1;", @"var i = 0, j = 1;"); - - TestCode(@"var i = 0, j = 1;", @"var i=0, j=1;", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); - } - - [TestMethod, Priority(0)] - public void TestLexcialDecl() { - TestCode(@"i=1", @"i = 1"); - } - - [TestMethod, Priority(0)] - public void TestForIn() { - TestCode( -@"for( var x in abc) { -}", -@"for (var x in abc) { -}"); - } - - [TestMethod, Priority(0)] - public void TestFor() { - var options = new FormattingOptions() { SpaceAfterSemiColonInFor = true }; - TestCode( -@"for (var i = 0;i < 10;i++) { -}", -@"for (var i = 0; i < 10; i++) { -}", - options); - - options = new FormattingOptions() { SpaceAfterSemiColonInFor = false }; - TestCode( -@"for (var i = 0; i < 10; i++) { -}", -@"for (var i = 0;i < 10;i++) { -}", - options); - } - - [TestMethod, Priority(0)] - public void TestSpaceAfterComma() { - var options = new FormattingOptions() { SpaceAfterComma = true }; - TestCode( -@" -1,2,3 -x(1,2,3) -function x(a,b,c) { -}", -@" -1, 2, 3 -x(1, 2, 3) -function x(a, b, c) { -}", - options); - - options = new FormattingOptions() { SpaceAfterComma = false }; - TestCode( -@" -1, 2, 3 -x(1, 2, 3) -function x(a, b, c) { -}", -@" -1,2,3 -x(1,2,3) -function x(a,b,c) { -}", - options); - } - - [TestMethod, Priority(0)] - public void TestSpaceAfterFunctionInAnonymousFunctions() { - var options = new FormattingOptions() { SpaceAfterFunctionInAnonymousFunctions = true }; - TestCode( -@" -x = function() { -}", -@" -x = function () { -}", - options); - - options = new FormattingOptions() { SpaceAfterFunctionInAnonymousFunctions = false }; - TestCode( -@" -x = function () { -}", -@" -x = function() { -}", - - options); - } - - [TestMethod, Priority(0)] - public void TestNestedSwitch() { - TestCode("switch (a){\r\n case 1: x += 2;\r\n case 2 : \r\n for (var i=0;i<10;i++)\r\ni --;\r\n}\r\n", - @"switch (a) { - case 1: x += 2; - case 2: - for (var i = 0; i < 10; i++) - i--; -} -"); - } - - [TestMethod, Priority(0)] - public void TestNestedIfs() { - TestCode(@"if(1)if(1)if(1)if(1) { - x += 2 -}", -@"if (1) if (1) if (1) if (1) { - x += 2 -}"); - - TestCode(@"if(1)if(1)if(1)if(1) {x += 2 -}", -@"if (1) if (1) if (1) if (1) { - x += 2 -}"); - } - - [TestMethod, Priority(0)] - public void TestSwitch() { - TestCode("switch (a){\r\ncase 1 : x+=2 ; break;\r\n case 2:{\r\n }\r\n}\r\n", - "switch (a) {\r\n case 1: x += 2; break;\r\n case 2: {\r\n }\r\n}\r\n"); - - TestCode( - "switch (x)\r\n { case 1: { var a }\r\n}", - "switch (x) {\r\n case 1: { var a }\r\n}" -); - - TestCode(@"switch(abc) { - case 1: x; -break; -}", -@"switch (abc) { - case 1: x; - break; -}"); - - TestCode(@"switch(abc) { - case 1: x; y; -break; -}", -@"switch (abc) { - case 1: x; y; - break; -}"); - - TestCode(@"switch(abc) { - case 1: x; y; -z; zz; -break; -}", -@"switch (abc) { - case 1: x; y; - z; zz; - break; -}"); - - TestCode( -@"switch(abc) { - case 42: break; -}", -@"switch (abc) { - case 42: break; -}" - ); - - TestCode( -@"switch(abc) { -case 42: break; -}", -@"switch (abc) { - case 42: break; -}" - ); - - TestCode(@"switch(abc) { - case 1: -x -break; -}", -@"switch (abc) { - case 1: - x - break; -}"); - - - } - - [TestMethod, Priority(0)] - public void TestNewLineBracesForFunctions() { - var options = new FormattingOptions() { OpenBracesOnNewLineForFunctions = true }; - - TestCode( -@"function x() { -}", -@"function x() -{ -}", - options); - - options = new FormattingOptions() { OpenBracesOnNewLineForFunctions = false }; - TestCode( -@"function x() -{ -}", -@"function x() { -}", - options); - } - - [TestMethod, Priority(0)] - public void TestInsertTabs() { - var options = new FormattingOptions() { SpacesPerIndent = null }; - TestCode( -@"switch (abc) { - case 42: break; -}", -"switch (abc) {\r\n\tcase 42: break;\r\n}", - options - ); - - TestCode( - "switch (abc) {\r\n\tcase 42: break;\r\n}", - "switch (abc) {\r\n\tcase 42: break;\r\n}", - options - ); - - TestCode( -@"switch (abc) { - case 42: break; -}", -"switch (abc) {\r\n\tcase 42: break;\r\n}", - options - ); - } - - [TestMethod, Priority(0)] - public void TestComments() { - // comments in weird spots can result in some slightly odd - // insertions or missing insertions. These aren't set in stone - // necessarily but these test cases make sure we're not doing - // anything particularly horrible. The current behavior is - // mostly driven by whether or not we're scanning forwards or - // backwards to replace a particular piece of white space. - var options = new FormattingOptions() { SpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis = true }; - TestCode( -@"if (/*comment*/true/*comment*/) { -}", -@"if (/*comment*/true /*comment*/) { -}", - options); - - TestCode( -@"switch (abc) /*comment*/ { - case 'abc': break; -}", -@"switch (abc) /*comment*/ { - case 'abc': break; -}"); - - TestCode( -@"switch (abc) /*comment*/ -{ - case 'abc': break; -}", -@"switch (abc) /*comment*/ { - case 'abc': break; -}"); - - TestCode( -@"var x = 1, /* comment */ - y = 2;", -@"var x = 1, /* comment */ - y = 2;" -); - - TestCode( -@"var x = 1, /* comment */y = 2;", -@"var x = 1, /* comment */ y = 2;" -); - - TestCode( -@"x = a/*comment*/+/*comment*/b;", -@"x = a /*comment*/+/*comment*/ b;" -); - - TestCode( -@"x = a/*comment*/+/*comment*/ - b;", -@"x = a /*comment*/+/*comment*/ - b;" -); - - TestCode( -@"x = a/*comment*/+ - /*comment*/b;", -@"x = a /*comment*/+ - /*comment*/ b;" -); - } - - [TestMethod, Priority(0)] - public void TestSingleLineComments() { - TestCode( -@"var x = {'abc':42, - 'bar':100 // foo -}", -@"var x = { - 'abc': 42, - 'bar': 100 // foo -}"); - - TestCode(@"if(true) -// test -test;", -@"if (true) - // test - test;"); - - TestCode(@"var x=function () { -//comment -return 1; -}", @"var x = function () { - //comment - return 1; -}"); - - TestCode( -@"if(foo) // bar - abc", -@"if (foo) // bar - abc"); - - TestCode( -@"switch (foo) // foo -{ -}", -@"switch (foo) // foo -{ -}"); - - TestCode( -@"if (foo) // foo -{ -}", -@"if (foo) // foo -{ -}"); - - TestCode( -@"if (foo) { // foo -}", -@"if (foo) { // foo -}"); - - TestCode( -@"if(foo) /*bar*/ // foo -{ -}", -@"if (foo) /*bar*/ // foo -{ -}"); - - TestCode( -@"if(foo/*comment*/) // foo -{ -}", -@"if (foo/*comment*/) // foo -{ -}"); - - - - TestCode( -@"if(foo) // foo -{ -} else // bar -{ -}", -@"if (foo) // foo -{ -} else // bar -{ -}"); - - TestCode( -@"if(foo) -{ -} else // bar -{ -}", -@"if (foo) { -} else // bar -{ -}"); - - TestCode( -@"var x = {'abc':42, - 'ba':100, // foo - 'quox':99 -}", -@"var x = { - 'abc': 42, - 'ba': 100, // foo - 'quox': 99 -}"); - - TestCode( -@"for(var x in []) // abc -{ -}", -@"for (var x in []) // abc -{ -}"); - - TestCode( -@"try { -} catch(abc) // comment -{ -}", -@"try { -} catch (abc) // comment -{ -}"); - - TestCode( -@"try { -} finally // comment -{ -}", -@"try { -} finally // comment -{ -}"); - - TestCode( -@"while(foo) // comment -{ -}", -@"while (foo) // comment -{ -}"); - - TestCode( -@"with(foo) // comment -{ -}", -@"with (foo) // comment -{ -}"); - - TestCode( -@"for(var i = 0; i < 10; i++) // comment -{ -}", -@"for (var i = 0; i < 10; i++) // comment -{ -}"); - - TestCode( -@"for(; ;) // comment -{ -}", -@"for (; ;) // comment -{ -}"); - - TestCode( -@"function f() // comment -{ -}", -@"function f() // comment -{ -}"); - - TestCode( -@"switch (true) { // comment - -}", -@"switch (true) { // comment - -}"); - - TestCode( -@"switch (true) // comment -{ -}", -@"switch (true) // comment -{ -}"); - - // https://nodejstools.codeplex.com/workitem/1571 - TestCode( -@"e(p, function (ep) { // encode, then write results to engine -writeToEngine(ep); -});", -@"e(p, function (ep) { // encode, then write results to engine - writeToEngine(ep); -});"); - } - - [TestMethod, Priority(0)] - public void TestInsertSpaces() { - var options = new FormattingOptions() { SpacesPerIndent = 2 }; - TestCode( -"switch (abc) {\r\n\tcase 42: break;\r\n}", -"switch (abc) {\r\n case 42: break;\r\n}", - options - ); - - TestCode( - "switch (abc) {\r\n\t\tcase 42: break;\r\n}", - "switch (abc) {\r\n case 42: break;\r\n}", - options - ); - - options = new FormattingOptions() { SpacesPerIndent = 6 }; - TestCode( - "switch (abc) {\r\n\tcase 42: break;\r\n}", - "switch (abc) {\r\n case 42: break;\r\n}", - options - ); - - TestCode( - "switch (abc) {\r\n case 42: break;\r\n}", - "switch (abc) {\r\n case 42: break;\r\n}", - options - ); - } - - [TestMethod, Priority(0)] - public void TestNewLineBracesForFlowControl() { - var options = new FormattingOptions() { OpenBracesOnNewLineForControl = true }; - TestCode( -@"switch (abc) { - case 42: break; -}", -@"switch (abc) -{ - case 42: break; -}", - options); - - TestCode( -@"do { -} while(true);", -@"do -{ -} while(true);", - options); - - TestCode( -@"while (true) { -}", -@"while (true) -{ -}", - options); - - TestCode( -@"with (true) { -}", -@"with (true) -{ -}", - options); - - TestCode( -@"for (var i = 0; i < 10; i++) { -}", -@"for (var i = 0; i < 10; i++) -{ -}", - options); - - TestCode( -@"for (var x in []) { -}", -@"for (var x in []) -{ -}", - options); - - TestCode( -@"if (true) { -}", -@"if (true) -{ -}", - options); - - TestCode( -@"if (true) { -} else { -}", -@"if (true) -{ -} else -{ -}", - options); - - TestCode( -@"try { -} finally { -}", -@"try -{ -} finally -{ -}", - options); - - TestCode( -@"try { -} catch(abc) { -}", -@"try -{ -} catch (abc) -{ -}", - options); - } - - [TestMethod, Priority(0)] - public void TestNewLineBracesForFlowControl2() { - var options = new FormattingOptions() { OpenBracesOnNewLineForControl = false }; - TestCode( - @"switch (abc) -{ - case 42: break; -}", - @"switch (abc) { - case 42: break; -}", - options); - - TestCode( -@"do -{ -} while(true);", -@"do { -} while(true);", - options); - - TestCode( -@"while (true) -{ -}", -@"while (true) { -}", - options); - - TestCode( -@"with (true) -{ -}", -@"with (true) { -}", - options); - - TestCode( -@"for (var i = 0; i < 10; i++) -{ -}", -@"for (var i = 0; i < 10; i++) { -}", - options); - - TestCode( -@"for (var x in []) -{ -}", -@"for (var x in []) { -}", - options); - - TestCode( -@"if (true) -{ -}", -@"if (true) { -}", - options); - - TestCode( -@"if (true) -{ -} else -{ -}", -@"if (true) { -} else { -}", - options); - - TestCode( -@"try -{ -} finally -{ -}", -@"try { -} finally { -}", - options); - - TestCode( -@"try -{ -} catch(abc) -{ -}", -@"try { -} catch (abc) { -}", - options); - } - - [TestMethod, Priority(0)] - public void TestIf() { - // https://nodejstools.codeplex.com/workitem/1175 - TestCode( -@"if (true){ -}else{ -}", -@"if (true) { -} else { -}"); - - TestCode( -@"if (true){ -} -else{ -}", -@"if (true) { -} -else { -}"); - - TestCode( -@"if(true) { -if (true){ -} -else{ -} -}", -@"if (true) { - if (true) { - } - else { - } -}"); - - TestCode( -@"if(true) { - if (true){ - } - else{ - } -}", -@"if (true) { - if (true) { - } - else { - } -}"); - - } - - [TestMethod, Priority(0)] - public void TestNestedBlock() { - TestCode( -@"do { -if (true) { -aa -} else { -bb -} -} while(true);", -@"do { - if (true) { - aa - } else { - bb - } -} while(true);"); - - TestCode(@"if(true) { - aa; bb; - cc; dd -}", -@"if (true) { - aa; bb; - cc; dd -}"); - - TestCode( -@"do { -for (var i = 0; i < 10; i++) { -} -} while(true);", -@"do { - for (var i = 0; i < 10; i++) { - } -} while(true);"); - - - TestCode( -@"do { -while (true) { -} -} while(true);", -@"do { - while (true) { - } -} while(true);"); - - TestCode( -@"do { -with (true) { -} -} while(true);", -@"do { - with (true) { - } -} while(true);"); - - TestCode( -@"do { -for (var x in []) { -} -} while(true);", -@"do { - for (var x in []) { - } -} while(true);"); - - TestCode( -@"do { -do { -} while (true); -} while(true);", -@"do { - do { - } while (true); -} while(true);"); - - TestCode( -@"do { -try { -} finally { -} -} while(true);", -@"do { - try { - } finally { - } -} while(true);"); - - TestCode( -@"do { -try { -} catch(arg) { -} -} while(true);", -@"do { - try { - } catch (arg) { - } -} while(true);"); - - TestCode( -@"do { -switch (abc) { -case 42: break; -} -} while(true);", -@"do { - switch (abc) { - case 42: break; - } -} while(true);"); - } - - [TestMethod, Priority(0)] - public void TestSpacesAroundBinaryOperator() { - TestCode("x+y", "x + y", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = true }); - TestCode("x+y", "x+y", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); - TestCode("x + y", "x + y", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = true }); - TestCode("x + y", "x+y", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); - TestCode("x+y+z", "x + y + z", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = true }); - TestCode("x+y+z", "x+y+z", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); - TestCode("x + y + z", "x + y + z", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = true }); - TestCode("x + y + z", "x+y+z", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); - } - - [TestMethod, Priority(0)] - public void TestSpaceAfterKeywordsInControlFlowStatements() { - TestCode( -@"if(true) { -}", -@"if (true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = true }); - - TestCode( -@"if(true) { -}", -@"if(true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); - - TestCode( -@"if (true) { -}", -@"if(true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); - - TestCode( -@"with(true) { -}", -@"with (true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = true }); - - TestCode( -@"with(true) { -}", -@"with(true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); - - TestCode( -@"with (true) { -}", -@"with(true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); - - TestCode( -@"while(true) { -}", -@"while (true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = true }); - - TestCode( -@"while(true) { -}", -@"while(true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); - - TestCode( -@"while (true) { -}", -@"while(true) { -}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); - } - - [TestMethod, Priority(0)] - public void TestSimple() { - TestCode( -@"do { -x -} while(true);", -@"do { - x -} while(true);"); - - TestCode( -@"do { -break -} while(true);", -@"do { - break -} while(true);"); - - TestCode( -@"do { -continue -} while(true);", -@"do { - continue -} while(true);"); - - TestCode( -@"do { -; -} while(true);", -@"do { - ; -} while(true);"); - - TestCode( -@"do { -debugger -} while(true);", -@"do { - debugger -} while(true);"); - - TestCode( -@"do { -var x = 100; -} while(true);", -@"do { - var x = 100; -} while(true);"); - - TestCode( -@"do { -return 42; -} while(true);", -@"do { - return 42; -} while(true);"); - - TestCode( -@"do { -throw null; -} while(true);", -@"do { - throw null; -} while(true);"); - } - - [TestMethod, Priority(0)] - public void TestFormatterNotReplacingAggressively() { - var code = -@"function f() { - function g() { - -} -}"; - - var edits = Formatter.GetEditsForDocument(code, null); - Assert.AreEqual(1, edits.Length); - Assert.AreEqual(43, edits[0].Start); - Assert.AreEqual(" ", edits[0].Text); - Assert.AreEqual(0, edits[0].Length); - } - - private static void TestCode(string code, string expected, FormattingOptions options = null) { - var firstFormat = FormatCode(code, options); - Assert.AreEqual(expected, firstFormat); - - // TODO: We should reenable this once we get this to work. At the time of removing this TestInvalidTrailingQuote - // failed due to this... - - // a second call to format on a formatted code should have no changes - //var secondFormat = FormatCode(firstFormat, options); - //Assert.AreEqual(firstFormat, secondFormat, "First and Second call to format had different results..."); - - } - - private static void TestCode(int position, char ch, string code, string expected, FormattingOptions options = null) { - Assert.AreEqual(expected, FormatCode(code, position, ch, options)); - } - - private static void TestCode(int start, int end, string code, string expected, FormattingOptions options = null) { - Assert.AreEqual(expected, FormatCode(code, start, end, options)); - } - - private static void TestEnter(int start, int end, string code, string expected, FormattingOptions options = null) { - Assert.AreEqual(expected, FormatEnter(code, start, end, options)); - } - - private static string FormatCode(string code, FormattingOptions options) { - var ast = new JSParser(code).Parse(new CodeSettings()); - var edits = Formatter.GetEditsForDocument(code, options); - return ApplyEdits(code, edits); - } - - private static string FormatCode(string code, int position, char ch, FormattingOptions options) { - var ast = new JSParser(code).Parse(new CodeSettings()); - var edits = Formatter.GetEditsAfterKeystroke(code, position, ch, options); - return ApplyEdits(code, edits); - } - - private static string FormatCode(string code, int start, int end, FormattingOptions options) { - var ast = new JSParser(code).Parse(new CodeSettings()); - var edits = Formatter.GetEditsForRange(code, start, end, options); - return ApplyEdits(code, edits); - } - - private static string FormatEnter(string code, int start, int end, FormattingOptions options) { - var ast = new JSParser(code).Parse(new CodeSettings()); - var edits = Formatter.GetEditsAfterEnter(code, start, end, options); - return ApplyEdits(code, edits); - } - - private static string ApplyEdits(string code, Edit[] edits) { - StringBuilder newCode = new StringBuilder(code); - int delta = 0; - foreach (var edit in edits) { - newCode.Remove(edit.Start + delta, edit.Length); - newCode.Insert(edit.Start + delta, edit.Text); - delta -= edit.Length; - delta += edit.Text.Length; - } - return newCode.ToString(); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.IO; +using System.Text; +using Microsoft.NodejsTools.Formatting; +using Microsoft.NodejsTools.Parsing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace AnalysisTests { + [TestClass] + public class FormatterTests { +#if FALSE + [TestMethod, Priority(0)] + public void ReformatDirectory() { + ReformatDirectory("C:\\Source\\Express\\node_modules\\", "C:\\Source\\Express\\formatted\\"); + } + + private void ReformatDirectory(string inp, string output) { + FormattingOptions options = new FormattingOptions() { SpacesPerIndent = 2, SpaceAfterFunctionInAnonymousFunctions = false }; + foreach (var file in Directory.GetFiles(inp, "*.js", SearchOption.AllDirectories)) { + var newCode = FormatCode(File.ReadAllText(file), options); + var outFile = Path.Combine(output, file.Substring(inp.Length)); + Directory.CreateDirectory(Path.GetDirectoryName(outFile)); + + File.WriteAllText(outFile, newCode); + } + } +#endif + + [ClassInitialize] + public static void ClassInititalize(TestContext context) { + AssertListener.Initialize(); + } + + [TestMethod, Priority(0)] + public void TestAnonymousFunction() { + TestCode( +@"a('hello', 15, function(err, res) { +console.log(err); + b(res, 55, function(err, res2) { +console.log(err); + }); +});", + @"a('hello', 15, function (err, res) { + console.log(err); + b(res, 55, function (err, res2) { + console.log(err); + }); +});", new FormattingOptions() { SpacesPerIndent = 2 }); + + } + + /// + /// https://nodejstools.codeplex.com/workitem/1351 + /// + [TestMethod, Priority(0)] + public void TestNestedFunctionAndArrayLiteral() { + TestCode( +@"var a = require('a'); +a.mount('/', [ +pipe.static({ root: 'b' }), +pipe.proxy({ +'a': 1 +}) +]);", + @"var a = require('a'); +a.mount('/', [ + pipe.static({ root: 'b' }), + pipe.proxy({ + 'a': 1 + }) +]);"); + + TestCode( +@"switch (abc) { + case foo: function a() { + x = 42; y = 100;[1, + 2, 3] + } +}", + @"switch (abc) { + case foo: function a() { + x = 42; y = 100; [1, + 2, 3] + } +}"); + } + + [TestMethod, Priority(0)] + public void TestJsonArray() { + TestCode( +@"(function (seedData) { + seedData.initialNotes = [{ + name: ""category"", + notes: [{ + note: ""note1"", + author: ""author1"", + color: ""blue"" + }, + { + note: ""note1"", + author: ""author2"", + color: ""green"" + }] + }]; +})(module.exports);", +@"(function (seedData) { + seedData.initialNotes = [{ + name: ""category"", + notes: [{ + note: ""note1"", + author: ""author1"", + color: ""blue"" + }, + { + note: ""note1"", + author: ""author2"", + color: ""green"" + }] + }]; +})(module.exports);" + + ); + } + + [TestMethod, Priority(0)] + public void TestFormatAfterInvalidKey() { + Assert.AreEqual(0, Formatter.GetEditsAfterKeystroke("function f () { }", 0, ':').Length); + } + + /// + /// + /// + [TestMethod, Priority(0)] + public void TestSemicolonEndOfDocument() { + var testCode = new[] { + // https://nodejstools.codeplex.com/workitem/1102 + new { Before = "x=1\r\nconsole.log( 'hi');", After = "x=1\r\nconsole.log('hi');" }, + // https://nodejstools.codeplex.com/workitem/1075 + new { Before = "function hello () {\r\n return;", After = "function hello() {\r\n return;" } + }; + + foreach (var test in testCode) { + var code = test.Before; + Console.WriteLine(code); + + TestCode(code.IndexOf(';') + 1, ';', code, test.After); + } + } + + [TestMethod, Priority(0)] + public void TestFormatAfterEnter() { + var testCode = new[] { + // https://nodejstools.codeplex.com/workitem/1078 + new { Before = "function f() {\r\n if(true)!\r\n}", After = "function f() {\r\n if (true)\r\n\r\n}" }, + // https://nodejstools.codeplex.com/workitem/1075 + new { Before = "function hello() {!", After = "function hello() {\r\n" }, + // https://nodejstools.codeplex.com/workitem/1136 + new { Before = ";\r\n{\r\n /*!\r\n}", After = ";\r\n{\r\n /*\r\n\r\n}" }, + // https://nodejstools.codeplex.com/workitem/1133 + new { Before = ";\r\nvar mailOptions = {\r\n from: 'blahblah',!", After = ";\r\nvar mailOptions = {\r\n from: 'blahblah',\r\n" }, + // https://nodejstools.codeplex.com/workitem/1175 + new { Before = ";\r\nif(true)\r\n if (true)!", After = ";\r\nif (true)\r\n if (true)\r\n" }, + }; + + foreach (var test in testCode) { + Console.WriteLine(test.Before); + string code = test.Before.Replace("!", "\r\n"); + int enterIndex = code.IndexOf(';') + 2; + var lines = code.Split(new[] { "\r\n" }, StringSplitOptions.None); + int startIndex = 0, endIndex = lines[0].Length + lines[1].Length + ("\r\n".Length * 2); + for (int i = 1; i < lines.Length; i++) { + if (enterIndex < endIndex) { + break; + } + + startIndex += lines[i - 1].Length + "\r\n".Length; + endIndex += lines[i].Length + "\r\n".Length; + } + + TestEnter(startIndex, endIndex, code, test.After); + } + } + + [TestMethod, Priority(0)] + public void TestFormatAfterSemiColon() { + string surroundingCode = "x=1"; + var testCode = new[] { + new { Before = "function f() { \r\n return 42;\r\n}", After = "function f() { \r\n return 42;\r\n}" }, + new { Before = "function f() { \r\n return 42 ;\r\n}", After = "function f() { \r\n return 42;\r\n}" }, + new { Before = "function f() { \r\n return ;\r\n}", After = "function f() { \r\n return;\r\n}" }, + new { Before = "throw 42;", After = "throw 42;" }, + new { Before = "throw ;", After = "throw;" }, + new { Before = "x=42;", After = "x = 42;" }, + new { Before = "x=42;", After = "x = 42;" }, + new { Before = "debugger ;", After = "debugger;" }, + new { Before = "while (true) { \r\n break ;\r\n}", After = "while (true) { \r\n break;\r\n}" }, + new { Before = "while (true) { \r\n continue ;\r\n}", After = "while (true) { \r\n continue;\r\n}" }, + new { Before = "var x=1,y=2;", After = "var x = 1, y = 2;" }, + // https://nodejstools.codeplex.com/workitem/1346 + new { Before = "while(true){\r\nconsole.log('hello';)\r\n}", After = "while(true){\r\nconsole.log('hello';)\r\n}"} + }; + + foreach (var test in testCode) { + Console.WriteLine(test.Before); + string code = surroundingCode + "\r\n" + test.Before + "\r\n" + surroundingCode; + string expected = surroundingCode + "\r\n" + test.After + "\r\n" + surroundingCode; + TestCode( + code.IndexOf(';') + 1, + ';', + code, + expected + ); + } + } + + [TestMethod, Priority(0)] + public void TestSemicolonOnOwnLine() { + // https://nodejstools.codeplex.com/workitem/1473 + TestCode( + @"function f() { + console.log('hi') +; +}", + @"function f() { + console.log('hi') + ; +}"); + + TestCode( + @"function f() { + console.log('hi') // comment + ; +}", + @"function f() { + console.log('hi') // comment + ; +}"); + } + + [TestMethod, Priority(0)] + public void TestFormattingFunctionAsArgumentToFunction() { + // Format dedenting first argument too much + // https://nodejstools.codeplex.com/workitem/1463 + TestCode( + @"g( + function f(a, b, c) + { + console.log('hi'); + console.log('bar') + } +)", + @"g( + function f(a, b, c) { + console.log('hi'); + console.log('bar') + } +)" + ); + // Format indenting second argument function too much + // https://nodejstools.codeplex.com/workitem/1459 + TestCode( + @"g(function () { + console.log('hi') +}, function () { + console.log('toofar') + });", + @"g(function () { + console.log('hi') +}, function () { + console.log('toofar') +});"); + + TestCode( +@"g( + function () { + console.log('hi') + }, function () { + console.log('hi2') + });", +@"g( + function () { + console.log('hi') + }, function () { + console.log('hi2') + });"); + } + + [TestMethod, Priority(0)] + public void TestArrayLiteral() { + var testCode = new[] { + // https://nodejstools.codeplex.com/workitem/1474 + new { Before = "function f() {\r\n console.log('hi')\r\n x = [1,2,3]\r\n}", + After = "function f() {\r\n console.log('hi')\r\n x = [1, 2, 3]\r\n}"}, + new { Before = "function f() {\r\n console.log('hi')\r\n [1,2,3]\r\n}", + After = "function f() {\r\n console.log('hi')\r\n [1, 2, 3]\r\n}"}, + new { Before = "g(\r\n function f(a, b, c)\r\n {\r\n console.log('hi');\r\n [1,2,3]\r\n }\r\n)", + After = "g(\r\n function f(a, b, c) {\r\n console.log('hi');\r\n [1, 2, 3]\r\n }\r\n)"}, + new { Before = "var x =[\r\n1, 2, 3\r\n ]", + After = "var x = [\r\n 1, 2, 3\r\n]"}, + // Test multiple lines stay aligned, but individual single line arrays fixed + new { Before = "function f() {\r\n var x = [[1],\r\n [2, 3],\r\n [3,4,5]]\r\n}", + After = "function f() {\r\n var x = [[1],\r\n [2, 3],\r\n [3, 4, 5]]\r\n}"}, + // https://nodejstools.codeplex.com/workitem/1494 We shouldn't push the 3 & 4 together + new { Before = "var x = [1,2,3 4]", + After = "var x = [1, 2, 3 4]"}, + }; + + foreach (var test in testCode) { + TestCode(test.Before, test.After); + } + } + + [TestMethod, Priority(0)] + public void TestFormatAfterBadFor() { + TestCode(@"function g() { + for(int i = 0; i<1000000; i++) { + }", + @"function g() { + for (int i = 0; i < 1000000; i++) { + }"); + + // https://nodejstools.codeplex.com/workitem/1475 + TestCode(@"for { + x = 2}", + @"for { + x = 2}"); + } + + [TestMethod, Priority(0)] + public void TestIndexingOnFollowingLine() { + // https://nodejstools.codeplex.com/workitem/1465 + TestCode( + @"g() +[]", + @"g() +[]"); + } + + /// + /// https://nodejstools.codeplex.com/workitem/1204 + /// + [TestMethod, Priority(0)] + public void TestShebang() { + TestCode("#!/usr/env/node\r\n function f() {\r\n}", "#!/usr/env/node\r\nfunction f() {\r\n}"); + } + + [TestMethod, Priority(0)] + public void TestInvalidTrailingQuote() { + TestCode("var x = foo()'", "var x = foo() '"); + TestCode("return 42'", "return 42 '"); + TestCode("continue'", "continue '"); + + TestCode("break'", "break '"); + TestCode("throw 42'", "throw 42 '"); + } + + [TestMethod, Priority(0)] + public void TestMember() { + TestCode(" a.b", "a.b"); + TestCode("a .b", "a.b"); + TestCode("a. b", "a.b"); + TestCode(" a . b", "a.b"); + } + + [TestMethod, Priority(0)] + public void TestInvalidMember() { + TestCode("x.42", "x.42"); + TestCode("x3.42", "x3.42"); + TestCode("x_.23", "x_.23"); + TestCode("x23.42", "x23.42"); + TestCode("x23.42m", "x23.42m"); + TestCode("x23.\"hello\"", "x23.\"hello\""); + } + + [TestMethod, Priority(0)] + public void TestCall() { + TestCode("a()", "a()"); + TestCode("a ()", "a()"); + TestCode("a( b)", "a(b)"); + TestCode("a(b )", "a(b)"); + TestCode("a( b )", "a(b)"); + TestCode("a(b,c)", "a(b, c)"); + TestCode("a(b, c)", "a(b, c)"); + TestCode("a(b, c )", "a(b, c)"); + //https://nodejstools.codeplex.com/workitem/1525 + TestCode( +@"socket.on(""disconnect"", function () { + var a = 2; +}).on('message', function () { + var b = 1; +});", +@"socket.on(""disconnect"", function () { + var a = 2; +}).on('message', function () { + var b = 1; +});"); + } + + [TestMethod, Priority(0)] + public void TestUnaryOperator() { + TestCode("typeof x", "typeof x"); + TestCode("delete x", "delete x"); + TestCode("void x", "void x"); + TestCode("++ x", "++x"); + } + + [TestMethod, Priority(0)] + public void TestEmpty() { + TestCode("if (true);", "if (true);"); + TestCode("if (true) ;", "if (true);"); + + // https://github.com/Microsoft/nodejstools/issues/24 + TestCode( +@"var x = function () { + if (a) { + 123455 + }; +};", +@"var x = function () { + if (a) { + 123455 + }; +};"); + + // Shouldn't insert additional space between semicolons. + TestCode( +@"function a() { + var a;; +}", +@"function a() { + var a;; +}"); + } + + [TestMethod, Priority(0)] + public void TestNew() { + TestCode("var x = new Blah();", "var x = new Blah();"); + + } + + [TestMethod, Priority(0)] + public void TestFormatRange() { + string surroundingCode = "x=1"; + var testCode = new[] { + new { Before = "function f() { \r\n return 42;\r\n}", After = "function f() {\r\n return 42;\r\n}" }, + new { Before = "throw 42;", After = "throw 42;" }, + new { Before = "throw ;", After = "throw;" }, + new { Before = "x=42;", After = "x = 42;" }, + new { Before = "x=42;", After = "x = 42;" }, + new { Before = "debugger ;", After = "debugger;" }, + new { Before = "while (true) { \r\n break ;\r\n}", After = "while (true) {\r\n break;\r\n}" }, + new { Before = "while (true) { \r\n continue ;\r\n}", After = "while (true) {\r\n continue;\r\n}" }, + new { Before = "var x=1,y=2;", After = "var x = 1, y = 2;" }, + new { Before = "var x=1,y=2 ;", After = "var x = 1, y = 2;" }, + }; + + foreach (var test in testCode) { + Console.WriteLine(test.Before); + string code = surroundingCode + "\r\n" + test.Before + "\r\n" + surroundingCode; + string expected = surroundingCode + "\r\n" + test.After + "\r\n" + surroundingCode; + + // also check range + TestCode( + surroundingCode.Length, + code.Length - surroundingCode.Length, + code, + expected + ); + } + } + + [TestMethod, Priority(0)] + public void TestFormatAfterCloseBrace() { + string surroundingCode = "x=1"; + var testCode = new[] { + new { Before = "while(true) {\r\nblah\r\n!", After = "while (true) {\r\n blah\r\n}" }, + new { Before = "with(true) {\r\nblah\r\n!", After = "with (true) {\r\n blah\r\n}" }, + new { Before = "for(var i=0;i<10;i++) {\r\nblah\r\n!", After = "for (var i = 0; i < 10; i++) {\r\n blah\r\n}" }, + new { Before = "for(var x in []) {\r\nblah\r\n!", After = "for (var x in []) {\r\n blah\r\n}" }, + new { Before = "{\r\nblah\r\n!", After = "{\r\n blah\r\n}" }, + new { Before = "switch(abc){\r\ncase 42: return null;\r\n!", After = "switch (abc) {\r\n case 42: return null;\r\n}" }, + new { Before = "try {\r\nabc\r\n!", After = "try {\r\n abc\r\n}" }, + new { Before = "try {\r\nabc\r\n}catch(abc){\r\nabc\r\n!", After = "try {\r\n abc\r\n} catch (abc) {\r\n abc\r\n}" }, + new { Before = "try {\r\nabc\r\n}finally{\r\nabc\r\n!", After = "try {\r\n abc\r\n} finally {\r\n abc\r\n}" }, + new { Before = "{\r\n break;\r\n!", After = "{\r\n break;\r\n}" }, + new { Before = "{\r\n break ;\r\n!", After = "{\r\n break;\r\n}" }, + // https://nodejstools.codeplex.com/workitem/1346 + new { Before = "module.exports = {\r\n f: function () { console!\r\n}", After = "module.exports = {\r\n f: function () { console }\r\n}" }, + }; + + foreach (var test in testCode) { + Console.WriteLine(test.Before); + string indexCode = surroundingCode + "\r\n" + test.Before + "\r\n" + surroundingCode; + string code = surroundingCode + "\r\n" + test.Before.Replace('!', '}') + "\r\n" + surroundingCode; + string expected = surroundingCode + "\r\n" + test.After + "\r\n" + surroundingCode; + TestCode( + indexCode.IndexOf('!') + 1, + '}', + code, + expected + ); + } + } + + [TestMethod, Priority(0)] + public void TestLabeledStatement() { + TestCode(@"foo: { + 42; +}", + @"foo: { + 42; +}"); + + } + + [TestMethod, Priority(0)] + public void TestContinue() { + TestCode( +@"while (true) { + continue +}", +@"while (true) { + continue +}" +); + + TestCode( +@"while (true) { + continue +}", +@"while (true) { + continue +}" +); + TestCode( +@"while (true) { +label: + while (true) { + continue label + } +}", +@"while (true) { + label: + while (true) { + continue label + } +}" +); + } + + [TestMethod, Priority(0)] + public void TestBlock() { + TestCode( + "{\nvar b;\n}", + "{\n var b;\n}", + new FormattingOptions() { + NewLine = "\n" + }); + + TestCode( + "{\rvar b;\r}", + "{\r var b;\r}", + new FormattingOptions() { + NewLine = "\r" + } + ); + + TestCode( + "{\r\nvar b;\r\n}", + "{\r\n var b;\r\n}" + ); + } + + [TestMethod, Priority(0)] + public void TestBreak() { + TestCode( +@"while (true) { + break +}", +@"while (true) { + break +}" +); + + TestCode( +@"while (true) { + break +}", +@"while (true) { + break +}" +); + TestCode( +@"while (true) { + break}", +@"while (true) { + break +}" +); + + TestCode( +@"while (true) { +label: + while (true) { + break label + } +}", +@"while (true) { + label: + while (true) { + break label + } +}" +); + } + + [TestMethod, Priority(0)] + public void TestFunction() { + TestCode( +@"function f () { +}", +@"function f() { +}"); + // https://nodejstools.codeplex.com/workitem/1740 + TestCode( +@"exports.hugues = function(req,res){ res.render('hugues', { title: 'Hugues', year: new Date().getFullYear(), message: 'Your hugues page.' }); +};", +@"exports.hugues = function (req, res) { + res.render('hugues', { title: 'Hugues', year: new Date().getFullYear(), message: 'Your hugues page.' }); +};"); + } + + [TestMethod, Priority(0)] + public void TestReturn() { + TestCode( +@"function f() { + return +}", +@"function f() { + return +}" +); + + TestCode( +@"function f() { + return +}", +@"function f() { + return +}" +); + TestCode( +@"function f() { + return 42 +}", +@"function f() { + return 42 +}" +); + + TestCode( +@"function f() { + return 42; +}", +@"function f() { + return 42; +}" +); + } + + [TestMethod, Priority(0)] + public void TestYield() { + TestCode( +@"function *f() { + yield +}", +@"function* f() { + yield +}" +); + + TestCode( +@"function *f() { + yield +}", +@"function* f() { + yield +}" +); + TestCode( +@"function *f() { + yield 42 +}", +@"function* f() { + yield 42 +}" +); + + TestCode( +@"function *f() { + yield 42; +}", +@"function* f() { + yield 42; +}" +); + + TestCode( +@"function * f() { + yield 42; +}", +@"function* f() { + yield 42; +}" +); + + TestCode( +@"function * f() { + yield * 42; +}", +@"function* f() { + yield* 42; +}" +); + } + + [TestMethod, Priority(0)] + public void TestThrow() { + TestCode( +@"function f() { + throw +}", +@"function f() { + throw +}" +); + + TestCode( +@"function f() { + throw +}", +@"function f() { + throw +}" +); + TestCode( +@"function f() { + throw 42 +}", +@"function f() { + throw 42 +}" +); + + TestCode( +@"function f() { + throw 42; +}", +@"function f() { + throw 42; +}" +); + } + + [TestMethod, Priority(0)] + public void TestObjectLiteral() { + TestCode( +@"x = { get foo() { }, set foo(value) { } }", +@"x = { get foo() { }, set foo(value) { } }" +); + + + TestCode( +@"x = { }", +@"x = {}" +); + + TestCode( +@"x = { +}", +@"x = { +}" +); + + TestCode( +@"x = { +a: 42, +b: 100}", +@"x = { + a: 42, + b: 100 +}" + ); + + TestCode( +@"x = { +a: 42, b: 100, +c: 42, d: 100}", +@"x = { + a: 42, b: 100, + c: 42, d: 100 +}" + ); + TestCode( +@"x = {a:42, b:100}", +@"x = { a: 42, b: 100 }" + ); + + //https://nodejstools.codeplex.com/workitem/1525 + TestCode( +@"var a = function (test) { + return { + } +}", +@"var a = function (test) { + return { + } +}" +); + + //https://nodejstools.codeplex.com/workitem/1560 + TestCode( +@"var a = function (test) { + return { + + } +}", +@"var a = function (test) { + return { + + } +}" +); + } + + [TestMethod, Priority(0)] + public void TestIndentObjectLiteralWithoutComma() { + // https://nodejstools.codeplex.com/workitem/1782 + TestCode(@"Main.Test.prototype = { + testFunc: function () { + + }, + testFunc3: function () {} + testFunc2: function () { + + } +}", +@"Main.Test.prototype = { + testFunc: function () { + + }, + testFunc3: function () { } + testFunc2: function () { + + } +}"); + + TestCode(@"Main.Test.prototype = { + testFunc: function () { + + }, + testFunc3: function () { +} + testFunc2: function () { + + } +}", +@"Main.Test.prototype = { + testFunc: function () { + + }, + testFunc3: function () { + } + testFunc2: function () { + + } +}"); + } + + [TestMethod, Priority(0)] + public void TestDoWhile() { + TestCode(@"do + { var a +} while (1)", + @"do { + var a +} while (1)"); + + + } + + [TestMethod, Priority(0)] + public void TestControlFlowBraceCombo() { + var options = new FormattingOptions() { OpenBracesOnNewLineForControl = false, SpaceAfterKeywordsInControlFlowStatements = false }; + + TestCode( +@"do { +} while (true);", +@"do { +} while (true);", + options + ); + + TestCode( +@"try { +} finally { +}", +@"try { +} finally { +}", + options + ); + } + + [TestMethod, Priority(0)] + public void TestSpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis() { + var options = new FormattingOptions() { SpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis = true }; + + TestCode( +@"for (var x in abc) { +}", +@"for ( var x in abc ) { +}", + options + ); + + + TestCode( +@"for (var i = 0; i < 10; i++) { +}", +@"for ( var i = 0; i < 10; i++ ) { +}", + options + ); + + TestCode( +@"if (true) { +}", +@"if ( true ) { +}", + options + ); + + TestCode( +@"while (true) { +}", +@"while ( true ) { +}", + options + ); + + TestCode( +@"do { +} while (true);", +@"do { +} while ( true );", + options + ); + + TestCode( +@"try { +} catch (foo) { +}", +@"try { +} catch ( foo ) { +}", + options + ); + + TestCode( +@"function () { +}", +@"function () { +}", + options + ); + + TestCode( +@"function ( ) { +}", +@"function () { +}", + options + ); + + TestCode( +@"function (a) { +}", +@"function ( a ) { +}", + options + ); + + TestCode( +@"function (a, b) { +}", +@"function ( a, b ) { +}", + options + ); + + TestCode( +@"(a)", +@"( a )", + options + ); + + TestCode( +@"f(a)", +@"f( a )", + options + ); + + TestCode( +@"f(a, b)", +@"f( a, b )", + options + ); + + TestCode( +@"new f(a)", +@"new f( a )", + options + ); + + TestCode( +@"new f(a, b)", +@"new f( a, b )", + options + ); + + TestCode( +@"f[a]", +@"f[a]", + options + ); + + TestCode( +@"f[a, b]", +@"f[a, b]", + options + ); + + TestCode( +@"switch (abc) { + case 42: break; +}", +@"switch ( abc ) { + case 42: break; +}", + options + ); + + TestCode( +@"(x)", +@"( x )", + options + ); + } + + [TestMethod, Priority(0)] + public void TestSpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis2() { + var options = new FormattingOptions() { SpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis = false }; + + TestCode( +@"for ( var x in abc ) { +}", +@"for (var x in abc) { +}", + options + ); + + + TestCode( +@"for ( var i = 0; i < 10; i++ ) { +}", +@"for (var i = 0; i < 10; i++) { +}", + options + ); + + TestCode( +@"if ( true ) { +}", +@"if (true) { +}", + options + ); + + TestCode( +@"while ( true ) { +}", +@"while (true) { +}", + options + ); + + TestCode( +@"do { +} while ( true );", +@"do { +} while (true);", + options + ); + + TestCode( +@"try { +} catch ( foo ) { +}", +@"try { +} catch (foo) { +}", + options + ); + + TestCode( +@"function () { +}", +@"function () { +}", + options + ); + + + TestCode( +@"function ( a ) { +}", +@"function (a) { +}", + options + ); + + TestCode( +@"function ( a, b ) { +}", +@"function (a, b) { +}", + options + ); + + TestCode( +@"( a )", +@"(a)", + options + ); + + TestCode( +@"f( a )", +@"f(a)", + options + ); + + TestCode( +@"f( a, b )", +@"f(a, b)", + options + ); + + TestCode( +@"new f( a )", +@"new f(a)", + options + ); + + TestCode( +@"new f( a, b )", +@"new f(a, b)", + options + ); + + TestCode( +@"f[a]", +@"f[a]", + options + ); + + TestCode( +@"f[a, b]", +@"f[a, b]", + options + ); + + TestCode( +@"switch ( abc ) { + case 42: break; +}", +@"switch (abc) { + case 42: break; +}", + options + ); + + TestCode( +@"( x )", +@"(x)", + options + ); + } + + [TestMethod, Priority(0)] + public void TestArithmetic() { + TestCode("a+.0", "a + .0"); + } + + [TestMethod, Priority(0)] + public void TestVariableDecl() { + TestCode(@"var i = 0, j = 1;", @"var i = 0, j = 1;"); + TestCode(@"var i=0, j=1;", @"var i = 0, j = 1;"); + TestCode(@"var i = 0 , j = 1;", @"var i = 0, j = 1;"); + + TestCode(@"var i = 0, j = 1;", @"var i=0, j=1;", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); + } + + [TestMethod, Priority(0)] + public void TestLexcialDecl() { + TestCode(@"i=1", @"i = 1"); + } + + [TestMethod, Priority(0)] + public void TestForIn() { + TestCode( +@"for( var x in abc) { +}", +@"for (var x in abc) { +}"); + } + + [TestMethod, Priority(0)] + public void TestFor() { + var options = new FormattingOptions() { SpaceAfterSemiColonInFor = true }; + TestCode( +@"for (var i = 0;i < 10;i++) { +}", +@"for (var i = 0; i < 10; i++) { +}", + options); + + options = new FormattingOptions() { SpaceAfterSemiColonInFor = false }; + TestCode( +@"for (var i = 0; i < 10; i++) { +}", +@"for (var i = 0;i < 10;i++) { +}", + options); + } + + [TestMethod, Priority(0)] + public void TestSpaceAfterComma() { + var options = new FormattingOptions() { SpaceAfterComma = true }; + TestCode( +@" +1,2,3 +x(1,2,3) +function x(a,b,c) { +}", +@" +1, 2, 3 +x(1, 2, 3) +function x(a, b, c) { +}", + options); + + options = new FormattingOptions() { SpaceAfterComma = false }; + TestCode( +@" +1, 2, 3 +x(1, 2, 3) +function x(a, b, c) { +}", +@" +1,2,3 +x(1,2,3) +function x(a,b,c) { +}", + options); + } + + [TestMethod, Priority(0)] + public void TestSpaceAfterFunctionInAnonymousFunctions() { + var options = new FormattingOptions() { SpaceAfterFunctionInAnonymousFunctions = true }; + TestCode( +@" +x = function() { +}", +@" +x = function () { +}", + options); + + options = new FormattingOptions() { SpaceAfterFunctionInAnonymousFunctions = false }; + TestCode( +@" +x = function () { +}", +@" +x = function() { +}", + + options); + } + + [TestMethod, Priority(0)] + public void TestNestedSwitch() { + TestCode("switch (a){\r\n case 1: x += 2;\r\n case 2 : \r\n for (var i=0;i<10;i++)\r\ni --;\r\n}\r\n", + @"switch (a) { + case 1: x += 2; + case 2: + for (var i = 0; i < 10; i++) + i--; +} +"); + } + + [TestMethod, Priority(0)] + public void TestNestedIfs() { + TestCode(@"if(1)if(1)if(1)if(1) { + x += 2 +}", +@"if (1) if (1) if (1) if (1) { + x += 2 +}"); + + TestCode(@"if(1)if(1)if(1)if(1) {x += 2 +}", +@"if (1) if (1) if (1) if (1) { + x += 2 +}"); + } + + [TestMethod, Priority(0)] + public void TestSwitch() { + TestCode("switch (a){\r\ncase 1 : x+=2 ; break;\r\n case 2:{\r\n }\r\n}\r\n", + "switch (a) {\r\n case 1: x += 2; break;\r\n case 2: {\r\n }\r\n}\r\n"); + + TestCode( + "switch (x)\r\n { case 1: { var a }\r\n}", + "switch (x) {\r\n case 1: { var a }\r\n}" +); + + TestCode(@"switch(abc) { + case 1: x; +break; +}", +@"switch (abc) { + case 1: x; + break; +}"); + + TestCode(@"switch(abc) { + case 1: x; y; +break; +}", +@"switch (abc) { + case 1: x; y; + break; +}"); + + TestCode(@"switch(abc) { + case 1: x; y; +z; zz; +break; +}", +@"switch (abc) { + case 1: x; y; + z; zz; + break; +}"); + + TestCode( +@"switch(abc) { + case 42: break; +}", +@"switch (abc) { + case 42: break; +}" + ); + + TestCode( +@"switch(abc) { +case 42: break; +}", +@"switch (abc) { + case 42: break; +}" + ); + + TestCode(@"switch(abc) { + case 1: +x +break; +}", +@"switch (abc) { + case 1: + x + break; +}"); + + + } + + [TestMethod, Priority(0)] + public void TestNewLineBracesForFunctions() { + var options = new FormattingOptions() { OpenBracesOnNewLineForFunctions = true }; + + TestCode( +@"function x() { +}", +@"function x() +{ +}", + options); + + options = new FormattingOptions() { OpenBracesOnNewLineForFunctions = false }; + TestCode( +@"function x() +{ +}", +@"function x() { +}", + options); + } + + [TestMethod, Priority(0)] + public void TestInsertTabs() { + var options = new FormattingOptions() { SpacesPerIndent = null }; + TestCode( +@"switch (abc) { + case 42: break; +}", +"switch (abc) {\r\n\tcase 42: break;\r\n}", + options + ); + + TestCode( + "switch (abc) {\r\n\tcase 42: break;\r\n}", + "switch (abc) {\r\n\tcase 42: break;\r\n}", + options + ); + + TestCode( +@"switch (abc) { + case 42: break; +}", +"switch (abc) {\r\n\tcase 42: break;\r\n}", + options + ); + } + + [TestMethod, Priority(0)] + public void TestComments() { + // comments in weird spots can result in some slightly odd + // insertions or missing insertions. These aren't set in stone + // necessarily but these test cases make sure we're not doing + // anything particularly horrible. The current behavior is + // mostly driven by whether or not we're scanning forwards or + // backwards to replace a particular piece of white space. + var options = new FormattingOptions() { SpaceAfterOpeningAndBeforeClosingNonEmptyParenthesis = true }; + TestCode( +@"if (/*comment*/true/*comment*/) { +}", +@"if (/*comment*/true /*comment*/) { +}", + options); + + TestCode( +@"switch (abc) /*comment*/ { + case 'abc': break; +}", +@"switch (abc) /*comment*/ { + case 'abc': break; +}"); + + TestCode( +@"switch (abc) /*comment*/ +{ + case 'abc': break; +}", +@"switch (abc) /*comment*/ { + case 'abc': break; +}"); + + TestCode( +@"var x = 1, /* comment */ + y = 2;", +@"var x = 1, /* comment */ + y = 2;" +); + + TestCode( +@"var x = 1, /* comment */y = 2;", +@"var x = 1, /* comment */ y = 2;" +); + + TestCode( +@"x = a/*comment*/+/*comment*/b;", +@"x = a /*comment*/+/*comment*/ b;" +); + + TestCode( +@"x = a/*comment*/+/*comment*/ + b;", +@"x = a /*comment*/+/*comment*/ + b;" +); + + TestCode( +@"x = a/*comment*/+ + /*comment*/b;", +@"x = a /*comment*/+ + /*comment*/ b;" +); + } + + [TestMethod, Priority(0)] + public void TestSingleLineComments() { + TestCode( +@"var x = {'abc':42, + 'bar':100 // foo +}", +@"var x = { + 'abc': 42, + 'bar': 100 // foo +}"); + + TestCode(@"if(true) +// test +test;", +@"if (true) + // test + test;"); + + TestCode(@"var x=function () { +//comment +return 1; +}", @"var x = function () { + //comment + return 1; +}"); + + TestCode( +@"if(foo) // bar + abc", +@"if (foo) // bar + abc"); + + TestCode( +@"switch (foo) // foo +{ +}", +@"switch (foo) // foo +{ +}"); + + TestCode( +@"if (foo) // foo +{ +}", +@"if (foo) // foo +{ +}"); + + TestCode( +@"if (foo) { // foo +}", +@"if (foo) { // foo +}"); + + TestCode( +@"if(foo) /*bar*/ // foo +{ +}", +@"if (foo) /*bar*/ // foo +{ +}"); + + TestCode( +@"if(foo/*comment*/) // foo +{ +}", +@"if (foo/*comment*/) // foo +{ +}"); + + + + TestCode( +@"if(foo) // foo +{ +} else // bar +{ +}", +@"if (foo) // foo +{ +} else // bar +{ +}"); + + TestCode( +@"if(foo) +{ +} else // bar +{ +}", +@"if (foo) { +} else // bar +{ +}"); + + TestCode( +@"var x = {'abc':42, + 'ba':100, // foo + 'quox':99 +}", +@"var x = { + 'abc': 42, + 'ba': 100, // foo + 'quox': 99 +}"); + + TestCode( +@"for(var x in []) // abc +{ +}", +@"for (var x in []) // abc +{ +}"); + + TestCode( +@"try { +} catch(abc) // comment +{ +}", +@"try { +} catch (abc) // comment +{ +}"); + + TestCode( +@"try { +} finally // comment +{ +}", +@"try { +} finally // comment +{ +}"); + + TestCode( +@"while(foo) // comment +{ +}", +@"while (foo) // comment +{ +}"); + + TestCode( +@"with(foo) // comment +{ +}", +@"with (foo) // comment +{ +}"); + + TestCode( +@"for(var i = 0; i < 10; i++) // comment +{ +}", +@"for (var i = 0; i < 10; i++) // comment +{ +}"); + + TestCode( +@"for(; ;) // comment +{ +}", +@"for (; ;) // comment +{ +}"); + + TestCode( +@"function f() // comment +{ +}", +@"function f() // comment +{ +}"); + + TestCode( +@"switch (true) { // comment + +}", +@"switch (true) { // comment + +}"); + + TestCode( +@"switch (true) // comment +{ +}", +@"switch (true) // comment +{ +}"); + + // https://nodejstools.codeplex.com/workitem/1571 + TestCode( +@"e(p, function (ep) { // encode, then write results to engine +writeToEngine(ep); +});", +@"e(p, function (ep) { // encode, then write results to engine + writeToEngine(ep); +});"); + } + + [TestMethod, Priority(0)] + public void TestInsertSpaces() { + var options = new FormattingOptions() { SpacesPerIndent = 2 }; + TestCode( +"switch (abc) {\r\n\tcase 42: break;\r\n}", +"switch (abc) {\r\n case 42: break;\r\n}", + options + ); + + TestCode( + "switch (abc) {\r\n\t\tcase 42: break;\r\n}", + "switch (abc) {\r\n case 42: break;\r\n}", + options + ); + + options = new FormattingOptions() { SpacesPerIndent = 6 }; + TestCode( + "switch (abc) {\r\n\tcase 42: break;\r\n}", + "switch (abc) {\r\n case 42: break;\r\n}", + options + ); + + TestCode( + "switch (abc) {\r\n case 42: break;\r\n}", + "switch (abc) {\r\n case 42: break;\r\n}", + options + ); + } + + [TestMethod, Priority(0)] + public void TestNewLineBracesForFlowControl() { + var options = new FormattingOptions() { OpenBracesOnNewLineForControl = true }; + TestCode( +@"switch (abc) { + case 42: break; +}", +@"switch (abc) +{ + case 42: break; +}", + options); + + TestCode( +@"do { +} while(true);", +@"do +{ +} while(true);", + options); + + TestCode( +@"while (true) { +}", +@"while (true) +{ +}", + options); + + TestCode( +@"with (true) { +}", +@"with (true) +{ +}", + options); + + TestCode( +@"for (var i = 0; i < 10; i++) { +}", +@"for (var i = 0; i < 10; i++) +{ +}", + options); + + TestCode( +@"for (var x in []) { +}", +@"for (var x in []) +{ +}", + options); + + TestCode( +@"if (true) { +}", +@"if (true) +{ +}", + options); + + TestCode( +@"if (true) { +} else { +}", +@"if (true) +{ +} else +{ +}", + options); + + TestCode( +@"try { +} finally { +}", +@"try +{ +} finally +{ +}", + options); + + TestCode( +@"try { +} catch(abc) { +}", +@"try +{ +} catch (abc) +{ +}", + options); + } + + [TestMethod, Priority(0)] + public void TestNewLineBracesForFlowControl2() { + var options = new FormattingOptions() { OpenBracesOnNewLineForControl = false }; + TestCode( + @"switch (abc) +{ + case 42: break; +}", + @"switch (abc) { + case 42: break; +}", + options); + + TestCode( +@"do +{ +} while(true);", +@"do { +} while(true);", + options); + + TestCode( +@"while (true) +{ +}", +@"while (true) { +}", + options); + + TestCode( +@"with (true) +{ +}", +@"with (true) { +}", + options); + + TestCode( +@"for (var i = 0; i < 10; i++) +{ +}", +@"for (var i = 0; i < 10; i++) { +}", + options); + + TestCode( +@"for (var x in []) +{ +}", +@"for (var x in []) { +}", + options); + + TestCode( +@"if (true) +{ +}", +@"if (true) { +}", + options); + + TestCode( +@"if (true) +{ +} else +{ +}", +@"if (true) { +} else { +}", + options); + + TestCode( +@"try +{ +} finally +{ +}", +@"try { +} finally { +}", + options); + + TestCode( +@"try +{ +} catch(abc) +{ +}", +@"try { +} catch (abc) { +}", + options); + } + + [TestMethod, Priority(0)] + public void TestIf() { + // https://nodejstools.codeplex.com/workitem/1175 + TestCode( +@"if (true){ +}else{ +}", +@"if (true) { +} else { +}"); + + TestCode( +@"if (true){ +} +else{ +}", +@"if (true) { +} +else { +}"); + + TestCode( +@"if(true) { +if (true){ +} +else{ +} +}", +@"if (true) { + if (true) { + } + else { + } +}"); + + TestCode( +@"if(true) { + if (true){ + } + else{ + } +}", +@"if (true) { + if (true) { + } + else { + } +}"); + + } + + [TestMethod, Priority(0)] + public void TestNestedBlock() { + TestCode( +@"do { +if (true) { +aa +} else { +bb +} +} while(true);", +@"do { + if (true) { + aa + } else { + bb + } +} while(true);"); + + TestCode(@"if(true) { + aa; bb; + cc; dd +}", +@"if (true) { + aa; bb; + cc; dd +}"); + + TestCode( +@"do { +for (var i = 0; i < 10; i++) { +} +} while(true);", +@"do { + for (var i = 0; i < 10; i++) { + } +} while(true);"); + + + TestCode( +@"do { +while (true) { +} +} while(true);", +@"do { + while (true) { + } +} while(true);"); + + TestCode( +@"do { +with (true) { +} +} while(true);", +@"do { + with (true) { + } +} while(true);"); + + TestCode( +@"do { +for (var x in []) { +} +} while(true);", +@"do { + for (var x in []) { + } +} while(true);"); + + TestCode( +@"do { +do { +} while (true); +} while(true);", +@"do { + do { + } while (true); +} while(true);"); + + TestCode( +@"do { +try { +} finally { +} +} while(true);", +@"do { + try { + } finally { + } +} while(true);"); + + TestCode( +@"do { +try { +} catch(arg) { +} +} while(true);", +@"do { + try { + } catch (arg) { + } +} while(true);"); + + TestCode( +@"do { +switch (abc) { +case 42: break; +} +} while(true);", +@"do { + switch (abc) { + case 42: break; + } +} while(true);"); + } + + [TestMethod, Priority(0)] + public void TestSpacesAroundBinaryOperator() { + TestCode("x+y", "x + y", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = true }); + TestCode("x+y", "x+y", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); + TestCode("x + y", "x + y", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = true }); + TestCode("x + y", "x+y", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); + TestCode("x+y+z", "x + y + z", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = true }); + TestCode("x+y+z", "x+y+z", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); + TestCode("x + y + z", "x + y + z", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = true }); + TestCode("x + y + z", "x+y+z", new FormattingOptions() { SpaceBeforeAndAfterBinaryOperator = false }); + } + + [TestMethod, Priority(0)] + public void TestSpaceAfterKeywordsInControlFlowStatements() { + TestCode( +@"if(true) { +}", +@"if (true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = true }); + + TestCode( +@"if(true) { +}", +@"if(true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); + + TestCode( +@"if (true) { +}", +@"if(true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); + + TestCode( +@"with(true) { +}", +@"with (true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = true }); + + TestCode( +@"with(true) { +}", +@"with(true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); + + TestCode( +@"with (true) { +}", +@"with(true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); + + TestCode( +@"while(true) { +}", +@"while (true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = true }); + + TestCode( +@"while(true) { +}", +@"while(true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); + + TestCode( +@"while (true) { +}", +@"while(true) { +}", new FormattingOptions() { SpaceAfterKeywordsInControlFlowStatements = false }); + } + + [TestMethod, Priority(0)] + public void TestSimple() { + TestCode( +@"do { +x +} while(true);", +@"do { + x +} while(true);"); + + TestCode( +@"do { +break +} while(true);", +@"do { + break +} while(true);"); + + TestCode( +@"do { +continue +} while(true);", +@"do { + continue +} while(true);"); + + TestCode( +@"do { +; +} while(true);", +@"do { + ; +} while(true);"); + + TestCode( +@"do { +debugger +} while(true);", +@"do { + debugger +} while(true);"); + + TestCode( +@"do { +var x = 100; +} while(true);", +@"do { + var x = 100; +} while(true);"); + + TestCode( +@"do { +return 42; +} while(true);", +@"do { + return 42; +} while(true);"); + + TestCode( +@"do { +throw null; +} while(true);", +@"do { + throw null; +} while(true);"); + } + + [TestMethod, Priority(0)] + public void TestFormatterNotReplacingAggressively() { + var code = +@"function f() { + function g() { + +} +}"; + + var edits = Formatter.GetEditsForDocument(code, null); + Assert.AreEqual(1, edits.Length); + Assert.AreEqual(43, edits[0].Start); + Assert.AreEqual(" ", edits[0].Text); + Assert.AreEqual(0, edits[0].Length); + } + + private static void TestCode(string code, string expected, FormattingOptions options = null) { + var firstFormat = FormatCode(code, options); + Assert.AreEqual(expected, firstFormat); + + // TODO: We should reenable this once we get this to work. At the time of removing this TestInvalidTrailingQuote + // failed due to this... + + // a second call to format on a formatted code should have no changes + //var secondFormat = FormatCode(firstFormat, options); + //Assert.AreEqual(firstFormat, secondFormat, "First and Second call to format had different results..."); + + } + + private static void TestCode(int position, char ch, string code, string expected, FormattingOptions options = null) { + Assert.AreEqual(expected, FormatCode(code, position, ch, options)); + } + + private static void TestCode(int start, int end, string code, string expected, FormattingOptions options = null) { + Assert.AreEqual(expected, FormatCode(code, start, end, options)); + } + + private static void TestEnter(int start, int end, string code, string expected, FormattingOptions options = null) { + Assert.AreEqual(expected, FormatEnter(code, start, end, options)); + } + + private static string FormatCode(string code, FormattingOptions options) { + var ast = new JSParser(code).Parse(new CodeSettings()); + var edits = Formatter.GetEditsForDocument(code, options); + return ApplyEdits(code, edits); + } + + private static string FormatCode(string code, int position, char ch, FormattingOptions options) { + var ast = new JSParser(code).Parse(new CodeSettings()); + var edits = Formatter.GetEditsAfterKeystroke(code, position, ch, options); + return ApplyEdits(code, edits); + } + + private static string FormatCode(string code, int start, int end, FormattingOptions options) { + var ast = new JSParser(code).Parse(new CodeSettings()); + var edits = Formatter.GetEditsForRange(code, start, end, options); + return ApplyEdits(code, edits); + } + + private static string FormatEnter(string code, int start, int end, FormattingOptions options) { + var ast = new JSParser(code).Parse(new CodeSettings()); + var edits = Formatter.GetEditsAfterEnter(code, start, end, options); + return ApplyEdits(code, edits); + } + + private static string ApplyEdits(string code, Edit[] edits) { + StringBuilder newCode = new StringBuilder(code); + int delta = 0; + foreach (var edit in edits) { + newCode.Remove(edit.Start + delta, edit.Length); + newCode.Insert(edit.Start + delta, edit.Text); + delta -= edit.Length; + delta += edit.Text.Length; + } + return newCode.ToString(); + } + } +} diff --git a/Nodejs/Tests/AnalysisTests/ParserOffsetTests.cs b/Nodejs/Tests/AnalysisTests/ParserOffsetTests.cs index e39a51e5c..314e100eb 100644 --- a/Nodejs/Tests/AnalysisTests/ParserOffsetTests.cs +++ b/Nodejs/Tests/AnalysisTests/ParserOffsetTests.cs @@ -1,736 +1,736 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.NodejsTools.Parsing; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace AnalysisTests { - [TestClass] - public class ParserOffsetTests { - - [TestMethod, Priority(0)] - public void TestArrayLiteral() { - TestOneSnippet( - "[1,2,3]", - new NodeInfo(typeof(Block), 0, 7), - new NodeInfo(typeof(ExpressionStatement), 0, 7), - new NodeInfo(typeof(ArrayLiteral), 0, 7), - new NodeInfo(typeof(ConstantWrapper), 1, 2), - new NodeInfo(typeof(ConstantWrapper), 3, 4), - new NodeInfo(typeof(ConstantWrapper), 5, 6) - ); - } - - [TestMethod, Priority(0)] - public void TestBinaryOperator() { - TestOneSnippet( - "1 + 2", - new NodeInfo(typeof(Block), 0, 5), - new NodeInfo(typeof(ExpressionStatement), 0, 5), - new NodeInfo(typeof(BinaryOperator), 0, 5), - new NodeInfo(typeof(ConstantWrapper), 0, 1), - new NodeInfo(typeof(ConstantWrapper), 4, 5) - ); - TestOneSnippet( - "1+2", - new NodeInfo(typeof(Block), 0, 3), - new NodeInfo(typeof(ExpressionStatement), 0, 3), - new NodeInfo(typeof(BinaryOperator), 0, 3), - new NodeInfo(typeof(ConstantWrapper), 0, 1), - new NodeInfo(typeof(ConstantWrapper), 2, 3) - ); - } - - [TestMethod, Priority(0)] - public void TestLoopBreakContinue() { - TestOneSnippet( - @"for(var i = 0; i<10; i++) { - break; - continue; -}", - new NodeInfo(typeof(Block), 0, 57), - new NodeInfo(typeof(ForNode), 0, 57), - new NodeInfo(typeof(Var), 4, 13), - new NodeInfo(typeof(VariableDeclaration), 8, 13), - new NodeInfo(typeof(ConstantWrapper), 12, 13), - new NodeInfo(typeof(BinaryOperator), 15, 19), - new NodeInfo(typeof(Lookup), 15, 16), - new NodeInfo(typeof(ConstantWrapper), 17, 19), - new NodeInfo(typeof(Block), 26, 57), - new NodeInfo(typeof(Break), 33, 39), - new NodeInfo(typeof(ContinueNode), 45, 54), - new NodeInfo(typeof(UnaryOperator), 21, 24), - new NodeInfo(typeof(Lookup), 21, 22) - ); - } - - [TestMethod, Priority(0)] - public void TestCommaOperator() { - TestOneSnippet("1,2,3", - new NodeInfo(typeof(Block), 0, 5), - new NodeInfo(typeof(ExpressionStatement), 0, 5), - new NodeInfo(typeof(CommaOperator), 0, 5), - new NodeInfo(typeof(ConstantWrapper), 0, 1), - new NodeInfo(typeof(ConstantWrapper), 2, 3), - new NodeInfo(typeof(ConstantWrapper), 4, 5) - ); - } - - - [TestMethod, Priority(0)] - public void TestCallNode() { - TestOneSnippet("f(1,2,3)", - new NodeInfo(typeof(Block), 0, 8), - new NodeInfo(typeof(ExpressionStatement), 0, 8), - new NodeInfo(typeof(CallNode), 0, 8), - new NodeInfo(typeof(Lookup), 0, 1), - new NodeInfo(typeof(ConstantWrapper), 2, 3), - new NodeInfo(typeof(ConstantWrapper), 4, 5), - new NodeInfo(typeof(ConstantWrapper), 6, 7) - ); - - TestOneSnippet("new f(1,2,3)", - new NodeInfo(typeof(Block), 0, 12), - new NodeInfo(typeof(ExpressionStatement), 0, 12), - new NodeInfo(typeof(CallNode), 0, 12), - new NodeInfo(typeof(Lookup), 4, 5), - new NodeInfo(typeof(ConstantWrapper), 6, 7), - new NodeInfo(typeof(ConstantWrapper), 8, 9), - new NodeInfo(typeof(ConstantWrapper), 10, 11) - ); - - TestOneSnippet("f[0]", - new NodeInfo(typeof(Block), 0, 4), - new NodeInfo(typeof(ExpressionStatement), 0, 4), - new NodeInfo(typeof(CallNode), 0, 4), - new NodeInfo(typeof(Lookup), 0, 1), - new NodeInfo(typeof(ConstantWrapper), 2, 3) - ); - } - - [TestMethod, Priority(0)] - public void TestConditional() { - TestOneSnippet("true ? 1 : 0", - new NodeInfo(typeof(Block), 0, 12), - new NodeInfo(typeof(ExpressionStatement), 0, 12), - new NodeInfo(typeof(Conditional), 0, 12), - new NodeInfo(typeof(ConstantWrapper), 0, 4), - new NodeInfo(typeof(ConstantWrapper), 7, 8), - new NodeInfo(typeof(ConstantWrapper), 11, 12) - ); - - } - - [TestMethod, Priority(0)] - public void TestConstant() { - TestOneSnippet("'abc'", - new NodeInfo(typeof(Block), 0, 5), - new NodeInfo(typeof(ExpressionStatement), 0, 5), - new NodeInfo(typeof(DirectivePrologue), 0, 5) - ); - TestOneSnippet("10.0", - new NodeInfo(typeof(Block), 0, 4), - new NodeInfo(typeof(ExpressionStatement), 0, 4), - new NodeInfo(typeof(ConstantWrapper), 0, 4) - ); - } - - [TestMethod, Priority(0)] - public void TestConstStatement() { - TestOneSnippet("const x = 42;", - new NodeInfo(typeof(Block), 0, 13), - new NodeInfo(typeof(LexicalDeclaration), 0, 13), - new NodeInfo(typeof(VariableDeclaration), 6, 12), - new NodeInfo(typeof(ConstantWrapper), 10, 12) - ); - } - - [TestMethod, Priority(0)] - public void TestDebuggerStatement() { - TestOneSnippet("debugger;", - new NodeInfo(typeof(Block), 0, 9), - new NodeInfo(typeof(DebuggerNode), 0, 9) - ); - } - - [TestMethod, Priority(0)] - public void TestDoWhile() { - TestOneSnippet(@"do { -}while(true);", - new NodeInfo(typeof(Block), 0, 19), - new NodeInfo(typeof(DoWhile), 0, 18), - new NodeInfo(typeof(ConstantWrapper), 13, 17), - new NodeInfo(typeof(Block), 3, 7) - ); - } - - [TestMethod, Priority(0)] - public void TestEmptyStatement() { - TestOneSnippet(";", - new NodeInfo(typeof(Block), 0, 1), - new NodeInfo(typeof(EmptyStatement), 0, 1) - ); - } - - [TestMethod, Priority(0)] - public void TestForIn() { - TestOneSnippet(@"for(var x in abc) { -}", - new NodeInfo(typeof(Block), 0, 22), - new NodeInfo(typeof(ForIn), 0, 22), - new NodeInfo(typeof(Lookup), 13, 16), - new NodeInfo(typeof(Var), 4, 9), - new NodeInfo(typeof(VariableDeclaration), 8, 9), - new NodeInfo(typeof(Block), 18, 22) - - ); - } - - [TestMethod, Priority(0)] - public void TestFor() { - TestOneSnippet(@"for(var x = 0; x<100; x++) { -}", - new NodeInfo(typeof(Block), 0, 31), - new NodeInfo(typeof(ForNode), 0, 31), - new NodeInfo(typeof(Var), 4, 13), - new NodeInfo(typeof(VariableDeclaration), 8, 13), - new NodeInfo(typeof(ConstantWrapper), 12, 13), - new NodeInfo(typeof(BinaryOperator), 15, 20), - new NodeInfo(typeof(Lookup), 15, 16), - new NodeInfo(typeof(ConstantWrapper), 17, 20), - new NodeInfo(typeof(Block), 27, 31), - new NodeInfo(typeof(UnaryOperator), 22, 25), - new NodeInfo(typeof(Lookup), 22, 23) - ); - TestOneSnippet(@"for(x = 0; x<100; x++) { -}", - new NodeInfo(typeof(Block), 0, 27), - new NodeInfo(typeof(ForNode), 0, 27), - new NodeInfo(typeof(ExpressionStatement), 4, 9), - new NodeInfo(typeof(BinaryOperator), 4, 9), - new NodeInfo(typeof(Lookup), 4, 5), - new NodeInfo(typeof(ConstantWrapper), 8, 9), - new NodeInfo(typeof(BinaryOperator), 11, 16), - new NodeInfo(typeof(Lookup), 11, 12), - new NodeInfo(typeof(ConstantWrapper), 13, 16), - new NodeInfo(typeof(Block), 23, 27), - new NodeInfo(typeof(UnaryOperator), 18, 21), - new NodeInfo(typeof(Lookup), 18, 19) - ); - } - - [TestMethod, Priority(0)] - public void TestFunction() { - TestOneSnippet(@"function f(a, b, c) { -}", - new NodeInfo(typeof(Block), 0, 24), - new NodeInfo(typeof(FunctionObject), 0, 24, 9, 10, 19), - new NodeInfo(typeof(ParameterDeclaration), 11, 12), - new NodeInfo(typeof(ParameterDeclaration), 14, 15), - new NodeInfo(typeof(ParameterDeclaration), 17, 18), - new NodeInfo(typeof(Block), 20, 24) - ); - } - - [TestMethod, Priority(0)] - public void TestGetSet() { - TestOneSnippet(@"x = {get abc () { 42 }}", - new NodeInfo(typeof(Block), 0, 23), - new NodeInfo(typeof(ExpressionStatement), 0, 23), - new NodeInfo(typeof(BinaryOperator), 0, 23), - new NodeInfo(typeof(Lookup), 0, 1), - new NodeInfo(typeof(ObjectLiteral), 4, 23), - new NodeInfo(typeof(ObjectLiteralProperty), 5, 22), - new NodeInfo(typeof(GetterSetter), 5, 8), - new NodeInfo(typeof(FunctionExpression), 5, 22), - new NodeInfo(typeof(FunctionObject), 5, 22, 9, 13, 14), - new NodeInfo(typeof(Block), 16, 22), - new NodeInfo(typeof(ExpressionStatement), 18, 20), - new NodeInfo(typeof(ConstantWrapper), 18, 20) - ); - TestOneSnippet(@"x = {set abc (value) { }}", - new NodeInfo(typeof(Block), 0, 25), - new NodeInfo(typeof(ExpressionStatement), 0, 25), - new NodeInfo(typeof(BinaryOperator), 0, 25), - new NodeInfo(typeof(Lookup), 0, 1), - new NodeInfo(typeof(ObjectLiteral), 4, 25), - new NodeInfo(typeof(ObjectLiteralProperty), 5, 24), - new NodeInfo(typeof(GetterSetter), 5, 8), - new NodeInfo(typeof(FunctionExpression), 5, 24), - new NodeInfo(typeof(FunctionObject), 5, 24, 9, 13, 20), - new NodeInfo(typeof(ParameterDeclaration), 14, 19), - new NodeInfo(typeof(Block), 21, 24) - ); - } - - [TestMethod, Priority(0)] - public void TestGrouping() { - TestOneSnippet(@"(x)", - new NodeInfo(typeof(Block), 0, 3), - new NodeInfo(typeof(ExpressionStatement), 0, 3), - new NodeInfo(typeof(GroupingOperator), 0, 3), - new NodeInfo(typeof(Lookup), 1, 2) - ); - } - - [TestMethod, Priority(0)] - public void TestIf() { - TestOneSnippet(@"if(true) { -} else { -}", - new NodeInfo(typeof(Block), 0, 23), - new NodeInfo(typeof(IfNode), 0, 23), - new NodeInfo(typeof(ConstantWrapper), 3, 7), - new NodeInfo(typeof(Block), 9, 13), - new NodeInfo(typeof(Block), 19, 23) - ); - } - - [TestMethod, Priority(0)] - public void TestLabeledStatement() { - TestOneSnippet(@"myLabel: -console.log('hi');", - new NodeInfo(typeof(Block), 0, 28), - new NodeInfo(typeof(LabeledStatement), 0, 28), - new NodeInfo(typeof(ExpressionStatement), 10, 28), - new NodeInfo(typeof(CallNode), 10, 27), - new NodeInfo(typeof(Member), 10, 21), - new NodeInfo(typeof(Lookup), 10, 17), - new NodeInfo(typeof(ConstantWrapper), 22, 26) - ); - } - - [TestMethod, Priority(0)] - public void TestLexicalDeclaration() { - TestOneSnippet(@"'use strict'; -let x = 42;", - new NodeInfo(typeof(Block), 0, 26), - new NodeInfo(typeof(ExpressionStatement), 0, 12), - new NodeInfo(typeof(DirectivePrologue), 0, 13), - new NodeInfo(typeof(LexicalDeclaration), 15, 26), - new NodeInfo(typeof(VariableDeclaration), 19, 25), - new NodeInfo(typeof(ConstantWrapper), 23, 25) - ); - } - - [TestMethod, Priority(0)] - public void TestBogusLexicalDeclaration() { - TestOneSnippet(@"let x = 42;", - new NodeInfo(typeof(Block), 0, 11), - new NodeInfo(typeof(ExpressionStatement), 0, 3), - new NodeInfo(typeof(Lookup), 0, 3), - new NodeInfo(typeof(ExpressionStatement), 4, 11), - new NodeInfo(typeof(BinaryOperator), 4, 10), - new NodeInfo(typeof(Lookup), 4, 5), - new NodeInfo(typeof(ConstantWrapper), 8, 10) - ); - } - - [TestMethod, Priority(0)] - public void TestLookup() { - TestOneSnippet(@"x", - new NodeInfo(typeof(Block), 0, 1), - new NodeInfo(typeof(ExpressionStatement), 0, 1), - new NodeInfo(typeof(Lookup), 0, 1) - ); - TestOneSnippet(@"xyz", - new NodeInfo(typeof(Block), 0, 3), - new NodeInfo(typeof(ExpressionStatement), 0, 3), - new NodeInfo(typeof(Lookup), 0, 3) - ); - } - - [TestMethod, Priority(0)] - public void TestMember() { - TestOneSnippet(@"x.abc", - new NodeInfo(typeof(Block), 0, 5), - new NodeInfo(typeof(ExpressionStatement), 0, 5), - new NodeInfo(typeof(Member), 0, 5), - new NodeInfo(typeof(Lookup), 0, 1) - ); - } - - [TestMethod, Priority(0)] - public void TestObjectLiteral() { - TestOneSnippet(@"{abc:42, aaa:100}", - new NodeInfo(typeof(Block), 0, 17), - new NodeInfo(typeof(Block), 0, 17), - new NodeInfo(typeof(LabeledStatement), 1, 12), - new NodeInfo(typeof(ExpressionStatement), 5, 12), - new NodeInfo(typeof(CommaOperator), 5, 12), - new NodeInfo(typeof(ConstantWrapper), 5, 7), - new NodeInfo(typeof(Lookup), 9, 12) - ); - } - - [TestMethod, Priority(0)] - public void TestRegexpLiteral() { - TestOneSnippet(@"/foo/", - new NodeInfo(typeof(Block), 0, 5), - new NodeInfo(typeof(ExpressionStatement), 0, 5), - new NodeInfo(typeof(RegExpLiteral), 0, 5) - ); - } - - [TestMethod, Priority(0)] - public void TestReturn() { - TestOneSnippet(@"function f() { - return 42; -}", - new NodeInfo(typeof(Block), 0, 33), - new NodeInfo(typeof(FunctionObject), 0, 33, 9, 10, 11), - new NodeInfo(typeof(Block), 13, 33), - new NodeInfo(typeof(ReturnNode), 20, 30), - new NodeInfo(typeof(ConstantWrapper), 27, 29) - ); - TestOneSnippet(@"function f() { - return; -}", - new NodeInfo(typeof(Block), 0, 30), - new NodeInfo(typeof(FunctionObject), 0, 30, 9, 10, 11), - new NodeInfo(typeof(Block), 13, 30), - new NodeInfo(typeof(ReturnNode), 20, 27) - ); - } - - [TestMethod, Priority(0)] - public void TestSwitch() { - TestOneSnippet(@"switch(abc) { - case 42: - break; - case 'abc': - break; - default: - break; -}", - new NodeInfo(typeof(Block), 0, 109), - new NodeInfo(typeof(Switch), 0, 109), - new NodeInfo(typeof(Lookup), 7, 10), - new NodeInfo(typeof(SwitchCase), 19, 43), - new NodeInfo(typeof(ConstantWrapper), 24, 26), - new NodeInfo(typeof(Block), 37, 43), - new NodeInfo(typeof(Break), 37, 43), - new NodeInfo(typeof(SwitchCase), 49, 76), - new NodeInfo(typeof(ConstantWrapper), 54, 59), - new NodeInfo(typeof(Block), 70, 76), - new NodeInfo(typeof(Break), 70, 76), - new NodeInfo(typeof(SwitchCase), 82, 106), - new NodeInfo(typeof(Block), 100, 106), - new NodeInfo(typeof(Break), 100, 106) - ); - } - - [TestMethod, Priority(0)] - public void TestThis() { - TestOneSnippet(@"var x = this.foo;", - new NodeInfo(typeof(Block), 0, 17), - new NodeInfo(typeof(Var), 0, 17), - new NodeInfo(typeof(VariableDeclaration), 4, 16), - new NodeInfo(typeof(Member), 8, 16), - new NodeInfo(typeof(ThisLiteral), 8, 12) - ); - } - - [TestMethod, Priority(0)] - public void TestThrow() { - TestOneSnippet(@"throw 'error';", - new NodeInfo(typeof(Block), 0, 14), - new NodeInfo(typeof(ThrowNode), 0, 14), - new NodeInfo(typeof(ConstantWrapper), 6, 13) - ); - } - - [TestMethod, Priority(0)] - public void TestTryCatch() { - TestOneSnippet(@"try { - x -} catch(arg) { -}", - new NodeInfo(typeof(Block), 0, 31), - new NodeInfo(typeof(TryNode), 0, 31), - new NodeInfo(typeof(Block), 4, 15), - new NodeInfo(typeof(ExpressionStatement), 11, 12), - new NodeInfo(typeof(Lookup), 11, 12), - new NodeInfo(typeof(ParameterDeclaration), 22, 25), - new NodeInfo(typeof(Block), 27, 31) - ); - } - - [TestMethod, Priority(0)] - public void TestTryFinally() { - TestOneSnippet(@"try { - x -} finally { -}", - new NodeInfo(typeof(Block), 0, 28), - new NodeInfo(typeof(TryNode), 0, 28), - new NodeInfo(typeof(Block), 4, 15), - new NodeInfo(typeof(ExpressionStatement), 11, 12), - new NodeInfo(typeof(Lookup), 11, 12), - new NodeInfo(typeof(Block), 24, 28) - ); - } - - [TestMethod, Priority(0)] - public void TestTryCatchFinally() { - TestOneSnippet(@"try { - x -}catch(arg) { -} finally { -}", - new NodeInfo(typeof(Block), 0, 43), - new NodeInfo(typeof(TryNode), 0, 43), - new NodeInfo(typeof(Block), 4, 15), - new NodeInfo(typeof(ExpressionStatement), 11, 12), - new NodeInfo(typeof(Lookup), 11, 12), - new NodeInfo(typeof(ParameterDeclaration), 21, 24), - new NodeInfo(typeof(Block), 26, 30), - new NodeInfo(typeof(Block), 39, 43) - ); - } - - [TestMethod, Priority(0)] - public void TestVariableDeclaration() { - TestOneSnippet(@"var abc = 42, foo = 100;", - new NodeInfo(typeof(Block), 0, 24), - new NodeInfo(typeof(Var), 0, 24), - new NodeInfo(typeof(VariableDeclaration), 4, 12), - new NodeInfo(typeof(ConstantWrapper), 10, 12), - new NodeInfo(typeof(VariableDeclaration), 14, 23), - new NodeInfo(typeof(ConstantWrapper), 20, 23) - ); - } - - [TestMethod, Priority(0)] - public void TestUnaryOperator() { - TestOneSnippet(@"+42", - new NodeInfo(typeof(Block), 0, 3), - new NodeInfo(typeof(ExpressionStatement), 0, 3), - new NodeInfo(typeof(UnaryOperator), 0, 3), - new NodeInfo(typeof(ConstantWrapper), 1, 3) - ); - } - - [TestMethod, Priority(0)] - public void TestWhileNode() { - TestOneSnippet(@"while(foo) { hi; }", - new NodeInfo(typeof(Block), 0, 18), - new NodeInfo(typeof(WhileNode), 0, 18), - new NodeInfo(typeof(Lookup), 6, 9), - new NodeInfo(typeof(Block), 11, 18), - new NodeInfo(typeof(ExpressionStatement), 13, 16), - new NodeInfo(typeof(Lookup), 13, 15) - ); - } - - [TestMethod, Priority(0)] - public void TestWithNode() { - TestOneSnippet(@"with(foo) { hi; }", - new NodeInfo(typeof(Block), 0, 17), - new NodeInfo(typeof(WithNode), 0, 17), - new NodeInfo(typeof(Lookup), 5, 8), - new NodeInfo(typeof(Block), 10, 17), - new NodeInfo(typeof(ExpressionStatement), 12, 15), - new NodeInfo(typeof(Lookup), 12, 14) - ); - } - - [TestMethod, Priority(0)] - public void TestFunctionExpression() { - TestOneSnippet(@"x = function() { }", - new NodeInfo(typeof(Block), 0, 18), - new NodeInfo(typeof(ExpressionStatement), 0, 18), - new NodeInfo(typeof(BinaryOperator), 0, 18), - new NodeInfo(typeof(Lookup), 0, 1), - new NodeInfo(typeof(FunctionExpression), 4, 18), - new NodeInfo(typeof(FunctionObject), 4, 18, 0, 12, 13), - new NodeInfo(typeof(Block), 15, 18) - ); - } - - [TestMethod, Priority(0)] - public void TestFunctionExpressionWithName() { - TestOneSnippet(@"x = function name() { }", - new NodeInfo(typeof(Block), 0, 23), - new NodeInfo(typeof(ExpressionStatement), 0, 23), - new NodeInfo(typeof(BinaryOperator), 0, 23), - new NodeInfo(typeof(Lookup), 0, 1), - new NodeInfo(typeof(FunctionExpression), 4, 23), - new NodeInfo(typeof(FunctionObject), 4, 23, 13, 17, 18), - new NodeInfo(typeof(Block), 20, 23) - ); - } - - private void TestOneSnippet(string code, params NodeInfo[] nodes) { - bool success = false; - var result = ParseTreeWalker.Parse(code); - try { - Assert.AreEqual(nodes.Length, result.Count); - for (int i = 0; i < nodes.Length; i++) { - Assert.AreEqual(nodes[i], result[i]); - } - success = true; - } finally { - if (!success) { - for (int i = 0; i < result.Count; i++) { - Console.Write(result[i]); - if (i == result.Count - 1) { - Console.WriteLine(); - } else { - Console.WriteLine(","); - } - } - } - } - } - - class NodeInfo { - public readonly Type NodeType; - public readonly int Start, End; - public readonly int[] ExtraIndicies; - - public NodeInfo(Type type, int start, int end, params int[] extraIndicies) { - NodeType = type; - Start = start; - End = end; - ExtraIndicies = extraIndicies; - } - - public override int GetHashCode() { - // not a good hash code, but we don't need it, we never hash these. - return NodeType.GetHashCode(); - } - - public override bool Equals(object obj) { - NodeInfo other = obj as NodeInfo; - if (other == null) { - return false; - } - if (other.NodeType == NodeType && - other.Start == Start && - other.End == End) { - if (other.ExtraIndicies.Length == ExtraIndicies.Length) { - for (int i = 0; i < other.ExtraIndicies.Length; i++) { - if (other.ExtraIndicies[i] != ExtraIndicies[i]) { - return false; - } - } - return true; - } - return false; - } - return false; - } - - public override string ToString() { - if (ExtraIndicies == null || ExtraIndicies.Length == 0) { - return String.Format("new NodeInfo(typeof({0}), {1}, {2})", NodeType.Name, Start, End); - } - - var res = String.Format("new NodeInfo(typeof({0}), {1}, {2}", NodeType.Name, Start, End); - for (int i = 0; i < ExtraIndicies.Length; i++) { - res = res + ", " + ExtraIndicies[i]; - } - res += ")"; - return res; - } - } - - class ParseTreeWalker : AstVisitor { - public readonly List Nodes = new List(); - private readonly JsAst _tree; - - public ParseTreeWalker(JsAst tree) { - _tree = tree; - } - - public static List Parse(string code) { - var ast = ParseCode(code); - var walker = new ParseTreeWalker(ast); - ast.Walk(walker); - return walker.Nodes; - } - - private static JsAst ParseCode(string code) { - var parser = new JSParser(code); - var ast = parser.Parse(new CodeSettings()); - return ast; - } - - private void AddNode(Node node, params int[] extraIndicies) { - Nodes.Add( - new NodeInfo( - node.GetType(), - node.GetStartIndex(_tree.LocationResolver), - node.GetEndIndex(_tree.LocationResolver), - extraIndicies - ) - ); - } - - public override bool Walk(ArrayLiteral node) { AddNode(node); return true; } - public override bool Walk(BinaryOperator node) { AddNode(node); return true; } - public override bool Walk(CommaOperator node) { AddNode(node); return true; } - public override bool Walk(Block node) { AddNode(node); return true; } - public override bool Walk(Break node) { AddNode(node); return true; } - public override bool Walk(CallNode node) { AddNode(node); return true; } - public override bool Walk(Conditional node) { AddNode(node); return true; } - public override bool Walk(ConstantWrapper node) { AddNode(node); return true; } - public override bool Walk(ConstStatement node) { AddNode(node); return true; } - public override bool Walk(ContinueNode node) { AddNode(node); return true; } - public override bool Walk(DebuggerNode node) { AddNode(node); return true; } - public override bool Walk(DirectivePrologue node) { AddNode(node); return true; } - public override bool Walk(DoWhile node) { AddNode(node); return true; } - public override bool Walk(EmptyStatement node) { AddNode(node); return true; } - public override bool Walk(ForIn node) { AddNode(node); return true; } - public override bool Walk(ForNode node) { AddNode(node); return true; } - public override bool Walk(FunctionObject node) { AddNode(node, node.GetNameSpan(_tree.LocationResolver).Start, node.ParameterStart, node.ParameterEnd); return true; } - public override bool Walk(GetterSetter node) { AddNode(node); return true; } - public override bool Walk(GroupingOperator node) { AddNode(node); return true; } - public override bool Walk(IfNode node) { AddNode(node); return true; } - public override bool Walk(LabeledStatement node) { AddNode(node); return true; } - public override bool Walk(LexicalDeclaration node) { AddNode(node); return true; } - public override bool Walk(Lookup node) { AddNode(node); return true; } - public override bool Walk(Member node) { AddNode(node); return true; } - public override bool Walk(ObjectLiteral node) { AddNode(node); return true; } - public override bool Walk(ObjectLiteralField node) { AddNode(node); return true; } - public override bool Walk(ObjectLiteralProperty node) { AddNode(node); return true; } - public override bool Walk(ParameterDeclaration node) { AddNode(node); return true; } - public override bool Walk(RegExpLiteral node) { AddNode(node); return true; } - public override bool Walk(ReturnNode node) { AddNode(node); return true; } - public override bool Walk(Switch node) { AddNode(node); return true; } - public override bool Walk(SwitchCase node) { AddNode(node); return true; } - public override bool Walk(ThisLiteral node) { AddNode(node); return true; } - public override bool Walk(ThrowNode node) { AddNode(node); return true; } - public override bool Walk(TryNode node) { AddNode(node); return true; } - public override bool Walk(Var node) { AddNode(node); return true; } - public override bool Walk(VariableDeclaration node) { AddNode(node); return true; } - public override bool Walk(UnaryOperator node) { AddNode(node); return true; } - public override bool Walk(WhileNode node) { AddNode(node); return true; } - public override bool Walk(WithNode node) { AddNode(node); return true; } - public override bool Walk(JsAst jsAst) { return true; } - public override bool Walk(FunctionExpression functionExpression) { AddNode(functionExpression); return true; } - public override bool Walk(ExpressionStatement node) { AddNode(node); return true; } - - } - } - -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.NodejsTools.Parsing; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace AnalysisTests { + [TestClass] + public class ParserOffsetTests { + + [TestMethod, Priority(0)] + public void TestArrayLiteral() { + TestOneSnippet( + "[1,2,3]", + new NodeInfo(typeof(Block), 0, 7), + new NodeInfo(typeof(ExpressionStatement), 0, 7), + new NodeInfo(typeof(ArrayLiteral), 0, 7), + new NodeInfo(typeof(ConstantWrapper), 1, 2), + new NodeInfo(typeof(ConstantWrapper), 3, 4), + new NodeInfo(typeof(ConstantWrapper), 5, 6) + ); + } + + [TestMethod, Priority(0)] + public void TestBinaryOperator() { + TestOneSnippet( + "1 + 2", + new NodeInfo(typeof(Block), 0, 5), + new NodeInfo(typeof(ExpressionStatement), 0, 5), + new NodeInfo(typeof(BinaryOperator), 0, 5), + new NodeInfo(typeof(ConstantWrapper), 0, 1), + new NodeInfo(typeof(ConstantWrapper), 4, 5) + ); + TestOneSnippet( + "1+2", + new NodeInfo(typeof(Block), 0, 3), + new NodeInfo(typeof(ExpressionStatement), 0, 3), + new NodeInfo(typeof(BinaryOperator), 0, 3), + new NodeInfo(typeof(ConstantWrapper), 0, 1), + new NodeInfo(typeof(ConstantWrapper), 2, 3) + ); + } + + [TestMethod, Priority(0)] + public void TestLoopBreakContinue() { + TestOneSnippet( + @"for(var i = 0; i<10; i++) { + break; + continue; +}", + new NodeInfo(typeof(Block), 0, 57), + new NodeInfo(typeof(ForNode), 0, 57), + new NodeInfo(typeof(Var), 4, 13), + new NodeInfo(typeof(VariableDeclaration), 8, 13), + new NodeInfo(typeof(ConstantWrapper), 12, 13), + new NodeInfo(typeof(BinaryOperator), 15, 19), + new NodeInfo(typeof(Lookup), 15, 16), + new NodeInfo(typeof(ConstantWrapper), 17, 19), + new NodeInfo(typeof(Block), 26, 57), + new NodeInfo(typeof(Break), 33, 39), + new NodeInfo(typeof(ContinueNode), 45, 54), + new NodeInfo(typeof(UnaryOperator), 21, 24), + new NodeInfo(typeof(Lookup), 21, 22) + ); + } + + [TestMethod, Priority(0)] + public void TestCommaOperator() { + TestOneSnippet("1,2,3", + new NodeInfo(typeof(Block), 0, 5), + new NodeInfo(typeof(ExpressionStatement), 0, 5), + new NodeInfo(typeof(CommaOperator), 0, 5), + new NodeInfo(typeof(ConstantWrapper), 0, 1), + new NodeInfo(typeof(ConstantWrapper), 2, 3), + new NodeInfo(typeof(ConstantWrapper), 4, 5) + ); + } + + + [TestMethod, Priority(0)] + public void TestCallNode() { + TestOneSnippet("f(1,2,3)", + new NodeInfo(typeof(Block), 0, 8), + new NodeInfo(typeof(ExpressionStatement), 0, 8), + new NodeInfo(typeof(CallNode), 0, 8), + new NodeInfo(typeof(Lookup), 0, 1), + new NodeInfo(typeof(ConstantWrapper), 2, 3), + new NodeInfo(typeof(ConstantWrapper), 4, 5), + new NodeInfo(typeof(ConstantWrapper), 6, 7) + ); + + TestOneSnippet("new f(1,2,3)", + new NodeInfo(typeof(Block), 0, 12), + new NodeInfo(typeof(ExpressionStatement), 0, 12), + new NodeInfo(typeof(CallNode), 0, 12), + new NodeInfo(typeof(Lookup), 4, 5), + new NodeInfo(typeof(ConstantWrapper), 6, 7), + new NodeInfo(typeof(ConstantWrapper), 8, 9), + new NodeInfo(typeof(ConstantWrapper), 10, 11) + ); + + TestOneSnippet("f[0]", + new NodeInfo(typeof(Block), 0, 4), + new NodeInfo(typeof(ExpressionStatement), 0, 4), + new NodeInfo(typeof(CallNode), 0, 4), + new NodeInfo(typeof(Lookup), 0, 1), + new NodeInfo(typeof(ConstantWrapper), 2, 3) + ); + } + + [TestMethod, Priority(0)] + public void TestConditional() { + TestOneSnippet("true ? 1 : 0", + new NodeInfo(typeof(Block), 0, 12), + new NodeInfo(typeof(ExpressionStatement), 0, 12), + new NodeInfo(typeof(Conditional), 0, 12), + new NodeInfo(typeof(ConstantWrapper), 0, 4), + new NodeInfo(typeof(ConstantWrapper), 7, 8), + new NodeInfo(typeof(ConstantWrapper), 11, 12) + ); + + } + + [TestMethod, Priority(0)] + public void TestConstant() { + TestOneSnippet("'abc'", + new NodeInfo(typeof(Block), 0, 5), + new NodeInfo(typeof(ExpressionStatement), 0, 5), + new NodeInfo(typeof(DirectivePrologue), 0, 5) + ); + TestOneSnippet("10.0", + new NodeInfo(typeof(Block), 0, 4), + new NodeInfo(typeof(ExpressionStatement), 0, 4), + new NodeInfo(typeof(ConstantWrapper), 0, 4) + ); + } + + [TestMethod, Priority(0)] + public void TestConstStatement() { + TestOneSnippet("const x = 42;", + new NodeInfo(typeof(Block), 0, 13), + new NodeInfo(typeof(LexicalDeclaration), 0, 13), + new NodeInfo(typeof(VariableDeclaration), 6, 12), + new NodeInfo(typeof(ConstantWrapper), 10, 12) + ); + } + + [TestMethod, Priority(0)] + public void TestDebuggerStatement() { + TestOneSnippet("debugger;", + new NodeInfo(typeof(Block), 0, 9), + new NodeInfo(typeof(DebuggerNode), 0, 9) + ); + } + + [TestMethod, Priority(0)] + public void TestDoWhile() { + TestOneSnippet(@"do { +}while(true);", + new NodeInfo(typeof(Block), 0, 19), + new NodeInfo(typeof(DoWhile), 0, 18), + new NodeInfo(typeof(ConstantWrapper), 13, 17), + new NodeInfo(typeof(Block), 3, 7) + ); + } + + [TestMethod, Priority(0)] + public void TestEmptyStatement() { + TestOneSnippet(";", + new NodeInfo(typeof(Block), 0, 1), + new NodeInfo(typeof(EmptyStatement), 0, 1) + ); + } + + [TestMethod, Priority(0)] + public void TestForIn() { + TestOneSnippet(@"for(var x in abc) { +}", + new NodeInfo(typeof(Block), 0, 22), + new NodeInfo(typeof(ForIn), 0, 22), + new NodeInfo(typeof(Lookup), 13, 16), + new NodeInfo(typeof(Var), 4, 9), + new NodeInfo(typeof(VariableDeclaration), 8, 9), + new NodeInfo(typeof(Block), 18, 22) + + ); + } + + [TestMethod, Priority(0)] + public void TestFor() { + TestOneSnippet(@"for(var x = 0; x<100; x++) { +}", + new NodeInfo(typeof(Block), 0, 31), + new NodeInfo(typeof(ForNode), 0, 31), + new NodeInfo(typeof(Var), 4, 13), + new NodeInfo(typeof(VariableDeclaration), 8, 13), + new NodeInfo(typeof(ConstantWrapper), 12, 13), + new NodeInfo(typeof(BinaryOperator), 15, 20), + new NodeInfo(typeof(Lookup), 15, 16), + new NodeInfo(typeof(ConstantWrapper), 17, 20), + new NodeInfo(typeof(Block), 27, 31), + new NodeInfo(typeof(UnaryOperator), 22, 25), + new NodeInfo(typeof(Lookup), 22, 23) + ); + TestOneSnippet(@"for(x = 0; x<100; x++) { +}", + new NodeInfo(typeof(Block), 0, 27), + new NodeInfo(typeof(ForNode), 0, 27), + new NodeInfo(typeof(ExpressionStatement), 4, 9), + new NodeInfo(typeof(BinaryOperator), 4, 9), + new NodeInfo(typeof(Lookup), 4, 5), + new NodeInfo(typeof(ConstantWrapper), 8, 9), + new NodeInfo(typeof(BinaryOperator), 11, 16), + new NodeInfo(typeof(Lookup), 11, 12), + new NodeInfo(typeof(ConstantWrapper), 13, 16), + new NodeInfo(typeof(Block), 23, 27), + new NodeInfo(typeof(UnaryOperator), 18, 21), + new NodeInfo(typeof(Lookup), 18, 19) + ); + } + + [TestMethod, Priority(0)] + public void TestFunction() { + TestOneSnippet(@"function f(a, b, c) { +}", + new NodeInfo(typeof(Block), 0, 24), + new NodeInfo(typeof(FunctionObject), 0, 24, 9, 10, 19), + new NodeInfo(typeof(ParameterDeclaration), 11, 12), + new NodeInfo(typeof(ParameterDeclaration), 14, 15), + new NodeInfo(typeof(ParameterDeclaration), 17, 18), + new NodeInfo(typeof(Block), 20, 24) + ); + } + + [TestMethod, Priority(0)] + public void TestGetSet() { + TestOneSnippet(@"x = {get abc () { 42 }}", + new NodeInfo(typeof(Block), 0, 23), + new NodeInfo(typeof(ExpressionStatement), 0, 23), + new NodeInfo(typeof(BinaryOperator), 0, 23), + new NodeInfo(typeof(Lookup), 0, 1), + new NodeInfo(typeof(ObjectLiteral), 4, 23), + new NodeInfo(typeof(ObjectLiteralProperty), 5, 22), + new NodeInfo(typeof(GetterSetter), 5, 8), + new NodeInfo(typeof(FunctionExpression), 5, 22), + new NodeInfo(typeof(FunctionObject), 5, 22, 9, 13, 14), + new NodeInfo(typeof(Block), 16, 22), + new NodeInfo(typeof(ExpressionStatement), 18, 20), + new NodeInfo(typeof(ConstantWrapper), 18, 20) + ); + TestOneSnippet(@"x = {set abc (value) { }}", + new NodeInfo(typeof(Block), 0, 25), + new NodeInfo(typeof(ExpressionStatement), 0, 25), + new NodeInfo(typeof(BinaryOperator), 0, 25), + new NodeInfo(typeof(Lookup), 0, 1), + new NodeInfo(typeof(ObjectLiteral), 4, 25), + new NodeInfo(typeof(ObjectLiteralProperty), 5, 24), + new NodeInfo(typeof(GetterSetter), 5, 8), + new NodeInfo(typeof(FunctionExpression), 5, 24), + new NodeInfo(typeof(FunctionObject), 5, 24, 9, 13, 20), + new NodeInfo(typeof(ParameterDeclaration), 14, 19), + new NodeInfo(typeof(Block), 21, 24) + ); + } + + [TestMethod, Priority(0)] + public void TestGrouping() { + TestOneSnippet(@"(x)", + new NodeInfo(typeof(Block), 0, 3), + new NodeInfo(typeof(ExpressionStatement), 0, 3), + new NodeInfo(typeof(GroupingOperator), 0, 3), + new NodeInfo(typeof(Lookup), 1, 2) + ); + } + + [TestMethod, Priority(0)] + public void TestIf() { + TestOneSnippet(@"if(true) { +} else { +}", + new NodeInfo(typeof(Block), 0, 23), + new NodeInfo(typeof(IfNode), 0, 23), + new NodeInfo(typeof(ConstantWrapper), 3, 7), + new NodeInfo(typeof(Block), 9, 13), + new NodeInfo(typeof(Block), 19, 23) + ); + } + + [TestMethod, Priority(0)] + public void TestLabeledStatement() { + TestOneSnippet(@"myLabel: +console.log('hi');", + new NodeInfo(typeof(Block), 0, 28), + new NodeInfo(typeof(LabeledStatement), 0, 28), + new NodeInfo(typeof(ExpressionStatement), 10, 28), + new NodeInfo(typeof(CallNode), 10, 27), + new NodeInfo(typeof(Member), 10, 21), + new NodeInfo(typeof(Lookup), 10, 17), + new NodeInfo(typeof(ConstantWrapper), 22, 26) + ); + } + + [TestMethod, Priority(0)] + public void TestLexicalDeclaration() { + TestOneSnippet(@"'use strict'; +let x = 42;", + new NodeInfo(typeof(Block), 0, 26), + new NodeInfo(typeof(ExpressionStatement), 0, 12), + new NodeInfo(typeof(DirectivePrologue), 0, 13), + new NodeInfo(typeof(LexicalDeclaration), 15, 26), + new NodeInfo(typeof(VariableDeclaration), 19, 25), + new NodeInfo(typeof(ConstantWrapper), 23, 25) + ); + } + + [TestMethod, Priority(0)] + public void TestBogusLexicalDeclaration() { + TestOneSnippet(@"let x = 42;", + new NodeInfo(typeof(Block), 0, 11), + new NodeInfo(typeof(ExpressionStatement), 0, 3), + new NodeInfo(typeof(Lookup), 0, 3), + new NodeInfo(typeof(ExpressionStatement), 4, 11), + new NodeInfo(typeof(BinaryOperator), 4, 10), + new NodeInfo(typeof(Lookup), 4, 5), + new NodeInfo(typeof(ConstantWrapper), 8, 10) + ); + } + + [TestMethod, Priority(0)] + public void TestLookup() { + TestOneSnippet(@"x", + new NodeInfo(typeof(Block), 0, 1), + new NodeInfo(typeof(ExpressionStatement), 0, 1), + new NodeInfo(typeof(Lookup), 0, 1) + ); + TestOneSnippet(@"xyz", + new NodeInfo(typeof(Block), 0, 3), + new NodeInfo(typeof(ExpressionStatement), 0, 3), + new NodeInfo(typeof(Lookup), 0, 3) + ); + } + + [TestMethod, Priority(0)] + public void TestMember() { + TestOneSnippet(@"x.abc", + new NodeInfo(typeof(Block), 0, 5), + new NodeInfo(typeof(ExpressionStatement), 0, 5), + new NodeInfo(typeof(Member), 0, 5), + new NodeInfo(typeof(Lookup), 0, 1) + ); + } + + [TestMethod, Priority(0)] + public void TestObjectLiteral() { + TestOneSnippet(@"{abc:42, aaa:100}", + new NodeInfo(typeof(Block), 0, 17), + new NodeInfo(typeof(Block), 0, 17), + new NodeInfo(typeof(LabeledStatement), 1, 12), + new NodeInfo(typeof(ExpressionStatement), 5, 12), + new NodeInfo(typeof(CommaOperator), 5, 12), + new NodeInfo(typeof(ConstantWrapper), 5, 7), + new NodeInfo(typeof(Lookup), 9, 12) + ); + } + + [TestMethod, Priority(0)] + public void TestRegexpLiteral() { + TestOneSnippet(@"/foo/", + new NodeInfo(typeof(Block), 0, 5), + new NodeInfo(typeof(ExpressionStatement), 0, 5), + new NodeInfo(typeof(RegExpLiteral), 0, 5) + ); + } + + [TestMethod, Priority(0)] + public void TestReturn() { + TestOneSnippet(@"function f() { + return 42; +}", + new NodeInfo(typeof(Block), 0, 33), + new NodeInfo(typeof(FunctionObject), 0, 33, 9, 10, 11), + new NodeInfo(typeof(Block), 13, 33), + new NodeInfo(typeof(ReturnNode), 20, 30), + new NodeInfo(typeof(ConstantWrapper), 27, 29) + ); + TestOneSnippet(@"function f() { + return; +}", + new NodeInfo(typeof(Block), 0, 30), + new NodeInfo(typeof(FunctionObject), 0, 30, 9, 10, 11), + new NodeInfo(typeof(Block), 13, 30), + new NodeInfo(typeof(ReturnNode), 20, 27) + ); + } + + [TestMethod, Priority(0)] + public void TestSwitch() { + TestOneSnippet(@"switch(abc) { + case 42: + break; + case 'abc': + break; + default: + break; +}", + new NodeInfo(typeof(Block), 0, 109), + new NodeInfo(typeof(Switch), 0, 109), + new NodeInfo(typeof(Lookup), 7, 10), + new NodeInfo(typeof(SwitchCase), 19, 43), + new NodeInfo(typeof(ConstantWrapper), 24, 26), + new NodeInfo(typeof(Block), 37, 43), + new NodeInfo(typeof(Break), 37, 43), + new NodeInfo(typeof(SwitchCase), 49, 76), + new NodeInfo(typeof(ConstantWrapper), 54, 59), + new NodeInfo(typeof(Block), 70, 76), + new NodeInfo(typeof(Break), 70, 76), + new NodeInfo(typeof(SwitchCase), 82, 106), + new NodeInfo(typeof(Block), 100, 106), + new NodeInfo(typeof(Break), 100, 106) + ); + } + + [TestMethod, Priority(0)] + public void TestThis() { + TestOneSnippet(@"var x = this.foo;", + new NodeInfo(typeof(Block), 0, 17), + new NodeInfo(typeof(Var), 0, 17), + new NodeInfo(typeof(VariableDeclaration), 4, 16), + new NodeInfo(typeof(Member), 8, 16), + new NodeInfo(typeof(ThisLiteral), 8, 12) + ); + } + + [TestMethod, Priority(0)] + public void TestThrow() { + TestOneSnippet(@"throw 'error';", + new NodeInfo(typeof(Block), 0, 14), + new NodeInfo(typeof(ThrowNode), 0, 14), + new NodeInfo(typeof(ConstantWrapper), 6, 13) + ); + } + + [TestMethod, Priority(0)] + public void TestTryCatch() { + TestOneSnippet(@"try { + x +} catch(arg) { +}", + new NodeInfo(typeof(Block), 0, 31), + new NodeInfo(typeof(TryNode), 0, 31), + new NodeInfo(typeof(Block), 4, 15), + new NodeInfo(typeof(ExpressionStatement), 11, 12), + new NodeInfo(typeof(Lookup), 11, 12), + new NodeInfo(typeof(ParameterDeclaration), 22, 25), + new NodeInfo(typeof(Block), 27, 31) + ); + } + + [TestMethod, Priority(0)] + public void TestTryFinally() { + TestOneSnippet(@"try { + x +} finally { +}", + new NodeInfo(typeof(Block), 0, 28), + new NodeInfo(typeof(TryNode), 0, 28), + new NodeInfo(typeof(Block), 4, 15), + new NodeInfo(typeof(ExpressionStatement), 11, 12), + new NodeInfo(typeof(Lookup), 11, 12), + new NodeInfo(typeof(Block), 24, 28) + ); + } + + [TestMethod, Priority(0)] + public void TestTryCatchFinally() { + TestOneSnippet(@"try { + x +}catch(arg) { +} finally { +}", + new NodeInfo(typeof(Block), 0, 43), + new NodeInfo(typeof(TryNode), 0, 43), + new NodeInfo(typeof(Block), 4, 15), + new NodeInfo(typeof(ExpressionStatement), 11, 12), + new NodeInfo(typeof(Lookup), 11, 12), + new NodeInfo(typeof(ParameterDeclaration), 21, 24), + new NodeInfo(typeof(Block), 26, 30), + new NodeInfo(typeof(Block), 39, 43) + ); + } + + [TestMethod, Priority(0)] + public void TestVariableDeclaration() { + TestOneSnippet(@"var abc = 42, foo = 100;", + new NodeInfo(typeof(Block), 0, 24), + new NodeInfo(typeof(Var), 0, 24), + new NodeInfo(typeof(VariableDeclaration), 4, 12), + new NodeInfo(typeof(ConstantWrapper), 10, 12), + new NodeInfo(typeof(VariableDeclaration), 14, 23), + new NodeInfo(typeof(ConstantWrapper), 20, 23) + ); + } + + [TestMethod, Priority(0)] + public void TestUnaryOperator() { + TestOneSnippet(@"+42", + new NodeInfo(typeof(Block), 0, 3), + new NodeInfo(typeof(ExpressionStatement), 0, 3), + new NodeInfo(typeof(UnaryOperator), 0, 3), + new NodeInfo(typeof(ConstantWrapper), 1, 3) + ); + } + + [TestMethod, Priority(0)] + public void TestWhileNode() { + TestOneSnippet(@"while(foo) { hi; }", + new NodeInfo(typeof(Block), 0, 18), + new NodeInfo(typeof(WhileNode), 0, 18), + new NodeInfo(typeof(Lookup), 6, 9), + new NodeInfo(typeof(Block), 11, 18), + new NodeInfo(typeof(ExpressionStatement), 13, 16), + new NodeInfo(typeof(Lookup), 13, 15) + ); + } + + [TestMethod, Priority(0)] + public void TestWithNode() { + TestOneSnippet(@"with(foo) { hi; }", + new NodeInfo(typeof(Block), 0, 17), + new NodeInfo(typeof(WithNode), 0, 17), + new NodeInfo(typeof(Lookup), 5, 8), + new NodeInfo(typeof(Block), 10, 17), + new NodeInfo(typeof(ExpressionStatement), 12, 15), + new NodeInfo(typeof(Lookup), 12, 14) + ); + } + + [TestMethod, Priority(0)] + public void TestFunctionExpression() { + TestOneSnippet(@"x = function() { }", + new NodeInfo(typeof(Block), 0, 18), + new NodeInfo(typeof(ExpressionStatement), 0, 18), + new NodeInfo(typeof(BinaryOperator), 0, 18), + new NodeInfo(typeof(Lookup), 0, 1), + new NodeInfo(typeof(FunctionExpression), 4, 18), + new NodeInfo(typeof(FunctionObject), 4, 18, 0, 12, 13), + new NodeInfo(typeof(Block), 15, 18) + ); + } + + [TestMethod, Priority(0)] + public void TestFunctionExpressionWithName() { + TestOneSnippet(@"x = function name() { }", + new NodeInfo(typeof(Block), 0, 23), + new NodeInfo(typeof(ExpressionStatement), 0, 23), + new NodeInfo(typeof(BinaryOperator), 0, 23), + new NodeInfo(typeof(Lookup), 0, 1), + new NodeInfo(typeof(FunctionExpression), 4, 23), + new NodeInfo(typeof(FunctionObject), 4, 23, 13, 17, 18), + new NodeInfo(typeof(Block), 20, 23) + ); + } + + private void TestOneSnippet(string code, params NodeInfo[] nodes) { + bool success = false; + var result = ParseTreeWalker.Parse(code); + try { + Assert.AreEqual(nodes.Length, result.Count); + for (int i = 0; i < nodes.Length; i++) { + Assert.AreEqual(nodes[i], result[i]); + } + success = true; + } finally { + if (!success) { + for (int i = 0; i < result.Count; i++) { + Console.Write(result[i]); + if (i == result.Count - 1) { + Console.WriteLine(); + } else { + Console.WriteLine(","); + } + } + } + } + } + + class NodeInfo { + public readonly Type NodeType; + public readonly int Start, End; + public readonly int[] ExtraIndicies; + + public NodeInfo(Type type, int start, int end, params int[] extraIndicies) { + NodeType = type; + Start = start; + End = end; + ExtraIndicies = extraIndicies; + } + + public override int GetHashCode() { + // not a good hash code, but we don't need it, we never hash these. + return NodeType.GetHashCode(); + } + + public override bool Equals(object obj) { + NodeInfo other = obj as NodeInfo; + if (other == null) { + return false; + } + if (other.NodeType == NodeType && + other.Start == Start && + other.End == End) { + if (other.ExtraIndicies.Length == ExtraIndicies.Length) { + for (int i = 0; i < other.ExtraIndicies.Length; i++) { + if (other.ExtraIndicies[i] != ExtraIndicies[i]) { + return false; + } + } + return true; + } + return false; + } + return false; + } + + public override string ToString() { + if (ExtraIndicies == null || ExtraIndicies.Length == 0) { + return String.Format("new NodeInfo(typeof({0}), {1}, {2})", NodeType.Name, Start, End); + } + + var res = String.Format("new NodeInfo(typeof({0}), {1}, {2}", NodeType.Name, Start, End); + for (int i = 0; i < ExtraIndicies.Length; i++) { + res = res + ", " + ExtraIndicies[i]; + } + res += ")"; + return res; + } + } + + class ParseTreeWalker : AstVisitor { + public readonly List Nodes = new List(); + private readonly JsAst _tree; + + public ParseTreeWalker(JsAst tree) { + _tree = tree; + } + + public static List Parse(string code) { + var ast = ParseCode(code); + var walker = new ParseTreeWalker(ast); + ast.Walk(walker); + return walker.Nodes; + } + + private static JsAst ParseCode(string code) { + var parser = new JSParser(code); + var ast = parser.Parse(new CodeSettings()); + return ast; + } + + private void AddNode(Node node, params int[] extraIndicies) { + Nodes.Add( + new NodeInfo( + node.GetType(), + node.GetStartIndex(_tree.LocationResolver), + node.GetEndIndex(_tree.LocationResolver), + extraIndicies + ) + ); + } + + public override bool Walk(ArrayLiteral node) { AddNode(node); return true; } + public override bool Walk(BinaryOperator node) { AddNode(node); return true; } + public override bool Walk(CommaOperator node) { AddNode(node); return true; } + public override bool Walk(Block node) { AddNode(node); return true; } + public override bool Walk(Break node) { AddNode(node); return true; } + public override bool Walk(CallNode node) { AddNode(node); return true; } + public override bool Walk(Conditional node) { AddNode(node); return true; } + public override bool Walk(ConstantWrapper node) { AddNode(node); return true; } + public override bool Walk(ConstStatement node) { AddNode(node); return true; } + public override bool Walk(ContinueNode node) { AddNode(node); return true; } + public override bool Walk(DebuggerNode node) { AddNode(node); return true; } + public override bool Walk(DirectivePrologue node) { AddNode(node); return true; } + public override bool Walk(DoWhile node) { AddNode(node); return true; } + public override bool Walk(EmptyStatement node) { AddNode(node); return true; } + public override bool Walk(ForIn node) { AddNode(node); return true; } + public override bool Walk(ForNode node) { AddNode(node); return true; } + public override bool Walk(FunctionObject node) { AddNode(node, node.GetNameSpan(_tree.LocationResolver).Start, node.ParameterStart, node.ParameterEnd); return true; } + public override bool Walk(GetterSetter node) { AddNode(node); return true; } + public override bool Walk(GroupingOperator node) { AddNode(node); return true; } + public override bool Walk(IfNode node) { AddNode(node); return true; } + public override bool Walk(LabeledStatement node) { AddNode(node); return true; } + public override bool Walk(LexicalDeclaration node) { AddNode(node); return true; } + public override bool Walk(Lookup node) { AddNode(node); return true; } + public override bool Walk(Member node) { AddNode(node); return true; } + public override bool Walk(ObjectLiteral node) { AddNode(node); return true; } + public override bool Walk(ObjectLiteralField node) { AddNode(node); return true; } + public override bool Walk(ObjectLiteralProperty node) { AddNode(node); return true; } + public override bool Walk(ParameterDeclaration node) { AddNode(node); return true; } + public override bool Walk(RegExpLiteral node) { AddNode(node); return true; } + public override bool Walk(ReturnNode node) { AddNode(node); return true; } + public override bool Walk(Switch node) { AddNode(node); return true; } + public override bool Walk(SwitchCase node) { AddNode(node); return true; } + public override bool Walk(ThisLiteral node) { AddNode(node); return true; } + public override bool Walk(ThrowNode node) { AddNode(node); return true; } + public override bool Walk(TryNode node) { AddNode(node); return true; } + public override bool Walk(Var node) { AddNode(node); return true; } + public override bool Walk(VariableDeclaration node) { AddNode(node); return true; } + public override bool Walk(UnaryOperator node) { AddNode(node); return true; } + public override bool Walk(WhileNode node) { AddNode(node); return true; } + public override bool Walk(WithNode node) { AddNode(node); return true; } + public override bool Walk(JsAst jsAst) { return true; } + public override bool Walk(FunctionExpression functionExpression) { AddNode(functionExpression); return true; } + public override bool Walk(ExpressionStatement node) { AddNode(node); return true; } + + } + } + +} diff --git a/Nodejs/Tests/AnalysisTests/ParserTests.cs b/Nodejs/Tests/AnalysisTests/ParserTests.cs index fcc2da709..3f8bf33c5 100644 --- a/Nodejs/Tests/AnalysisTests/ParserTests.cs +++ b/Nodejs/Tests/AnalysisTests/ParserTests.cs @@ -1,5409 +1,5409 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Microsoft.NodejsTools.Parsing; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TestUtilities; - -namespace AnalysisTests { - [TestClass] - public class ParserTests { - [TestMethod, Priority(0)] - public void TestCodeSettings() { - var settings1 = new CodeSettings(); - var settings2 = new CodeSettings() { AllowShebangLine = true, SourceMode = JavaScriptSourceMode.Expression, StrictMode = true }; - var settings3 = new CodeSettings() { KnownGlobalNamesList = "foo;bar" }; - - foreach (var curSetting in new[] { settings1, settings2, settings3 }) { - var other = curSetting.Clone(); - Assert.AreEqual(curSetting.AllowShebangLine, other.AllowShebangLine); - Assert.AreEqual(curSetting.ConstStatementsMozilla, other.ConstStatementsMozilla); - Assert.AreEqual(curSetting.KnownGlobalNamesList, other.KnownGlobalNamesList); - Assert.AreEqual(curSetting.SourceMode, other.SourceMode); - Assert.AreEqual(curSetting.StrictMode, other.StrictMode); - } - } - - [TestMethod, Priority(0)] - public void TestSourceSpan() { - var span = new SourceSpan( - new SourceLocation(1, 1, 1), - new SourceLocation(100, 2, 100) - ); - - var span2 = span; - - Assert.IsTrue(span.Start < span.End); - AssertUtil.Throws(() => new SourceSpan(span.End, span.Start)); - AssertUtil.Throws(() => new SourceSpan(span.End, SourceLocation.Invalid)); - AssertUtil.Throws(() => new SourceSpan(SourceLocation.Invalid, span.Start)); - Assert.AreEqual(99, span.Length); - Assert.IsTrue(span == span2); - Assert.IsFalse(span != span2); - Assert.IsTrue(span.Equals(span2)); - } - - [Ignore] - [TestMethod, Priority(0)] - public void TestFunctionRecovery() { - const string code = @" -[1,2, -function Y() { - (abcde X()); -;abcde -} -] -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(33, 1)), - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(36, 1)), - new ErrorInfo(JSError.NoRightBracket, true, new IndexSpan(37, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(48, 1)) - ), - CheckBlock( - CheckExprStmt( - CheckArrayLiteral( - CheckConstant(1.0), - CheckConstant(2.0), - CheckFunctionExpr( - CheckFunctionObject( - "Y", - CheckBlock( - CheckExprStmt(CheckGrouping(CheckLookup("abcde"))), - CheckExprStmt(CheckCall(CheckLookup("X"))) - ) - ) - ) - ) - ), - CheckEmptyStmt(), - CheckLookupStmt("abcde") - ) - ); - } - - /// - /// https://nodejstools.codeplex.com/workitem/1194 - /// https://nodejstools.codeplex.com/workitem/1200 - /// - [TestMethod, Priority(0)] - public void TestParseUnterminatedFunctionInList() { - const string code = @"console.log(function(error, response) {"; - - CheckAst( - ParseCode( - code, - false, - new ErrorInfo(JSError.ErrorEndOfFile, true, new IndexSpan(39, 0)), - new ErrorInfo(JSError.UnclosedFunction, true, new IndexSpan(12, 25)), - new ErrorInfo(JSError.NoRightBracketOrComma, true, new IndexSpan(39, 0)) - ), - CheckBlock( - CheckExprStmt( - CheckCall( - CheckMember("log", CheckLookup("console")), - CheckFunctionExpr( - CheckFunctionObject( - null, - CheckBlock(), - CheckParameterDeclaration("error"), - CheckParameterDeclaration("response") - ) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestParseExpression() { - const string code = @" -42 -"; - CheckAst( - ParseExpression( - code, - false - ), - CheckBlock( - CheckExprStmt( - CheckConstant(42.0) - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void ErrorCallBadArgs() { - const string code = @" -foo(abc.'foo') -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(10, 5)) - ), - CheckBlock( - CheckExprStmt( - CheckCall( - CheckLookup("foo"), - CheckLookup("abc") - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorCallBadArgs2() { - const string code = @" -foo(abc.'foo' else function) -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(10, 5)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(29, 1)) - ), - CheckBlock( - CheckExprStmt( - CheckCall( - CheckLookup("foo"), - CheckLookup("abc") - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestMemberKeyword() { - const string code = @" -foo.get -"; - CheckAst( - ParseCode( - code - ), - CheckBlock( - CheckExprStmt( - CheckMember("get", CheckLookup("foo")) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorGroupingNoCloseParen() { - const string code = @" -(foo -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(8, 0)) - ), - CheckBlock( - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorGroupingNoSkipToken() { - const string code = @" -(else function) -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(3, 4)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(16, 1)) - ), - CheckBlock( - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorPostfixNoSkipToken() { - const string code = @" -(else++) -foo -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(3, 4)) - ), - CheckBlock( - ) - ); - } - - - [TestMethod, Priority(0)] - public void ErrorDoubleOverflow() { - const string code = @" -1e100000 -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NumericOverflow, true, new IndexSpan(2, 8)) - ), - CheckBlock( - CheckConstantStmt(new InvalidNumericErrorValue("1e100000")) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningDoubleBoundaries() { - const string code = @" -1.7976931348623157E+308; --1.7976931348623157E+308 -"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NumericMaximum, false, new IndexSpan(2, 23)), - new ErrorInfo(JSError.NumericMaximum, false, new IndexSpan(29, 23)) - ), - CheckBlock( - CheckConstantStmt(1.79769e+308), - CheckConstantStmt(-1.79769e+308) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorWithStatementBody() { - const string code = @" -with(abc) { - else -} -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(19, 4)) - ), - CheckBlock( - CheckWith( - CheckLookup("abc"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorWithStatementBody2() { - const string code = @" -with(abc) { - throw else function -} -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(25, 4)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(40, 1)) - ), - CheckBlock( - CheckWith( - CheckLookup("abc"), - CheckBlock( - CheckThrow(IsNullExpr) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningWithStatementNoCurlys() { - const string code = @" -with(abc) - foo -"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.StatementBlockExpected, false, new IndexSpan(18, 0)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(7, 3)) - ), - CheckBlock( - CheckWith( - CheckLookup("abc"), - CheckBlock( - CheckLookupStmt("foo") - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorWithStatement() { - const string code = @" -with(abc.'foo') { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(11, 5)) - ), - CheckBlock( - CheckWith( - CheckLookup("abc"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorWithStatement2() { - const string code = @" -with(else) { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(7, 4)) - ), - CheckBlock( - CheckWith( - CheckConstant(true), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorWithStatement3() { - const string code = @" -with(else function) { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(7, 4)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(20, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(25, 1)) - ), - CheckBlock( - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorWithStatementNoParens() { - const string code = @" -with abc { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(7, 3)), - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(11, 1)) - ), - CheckBlock( - CheckWith( - CheckLookup("abc"), - CheckBlock() - ) - ) - ); - } - - [Ignore] - [TestMethod, Priority(0)] - public void ErrorArrayLiteralBad() { - const string code = @" -i = [foo foo]"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoRightBracket, true, new IndexSpan(11, 3)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckArrayLiteral( - CheckLookup("foo") - ) - ) - ), - CheckLookupStmt("foo") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorArrayLiteralBad2() { - const string code = @" -i = [foo.'abc']"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(11, 5)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckArrayLiteral( - CheckLookup("foo") - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorArrayLiteralBad3() { - const string code = @" -i = [foo.'abc' else function]"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(11, 5)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(30, 1)) - ), - CheckBlock( - CheckExprStmt( - CheckLookup("i") - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ArrayLiteralEmpty() { - const string code = @" -i = []"; - CheckAst( - ParseCode( - code - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckArrayLiteral( - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ArrayLiteralMissing() { - const string code = @" -i = [1,,]"; - CheckAst( - ParseCode( - code - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckArrayLiteral( - One, - CheckConstant(Missing.Value), - CheckConstant(Missing.Value) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorFunctionBadBody3() { - const string code = @" -function foo() { - throw else function -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(30, 4)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(45, 1)) - ), - CheckBlock( - CheckFunctionObject( - "foo", - CheckBlock( - CheckThrow(IsNullExpr) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorFunctionBadBody2() { - const string code = @" -function foo() { - else function -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(24, 4)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(39, 1)) - ), - CheckBlock( - CheckFunctionObject( - "foo", - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorFunctionBadBody() { - const string code = @" -function foo() { - else -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(24, 4)) - ), - CheckBlock( - CheckFunctionObject( - "foo", - CheckBlock() - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void ErrorFunctionBadArgList2() { - const string code = @" -function foo(arg if) { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoComma, true, new IndexSpan(19, 2)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(26, 1)) - ), - CheckBlock( - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorFunctionTypeDefinition() { - const string code = @" -function foo(arg arg) { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoCommaOrTypeDefinitionError, true, new IndexSpan(19, 3)) - ), - CheckBlock( - CheckFunctionObject( - "foo", - CheckBlock(), - CheckParameterDeclaration("arg"), - CheckParameterDeclaration("arg") - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorFunctionBadArgList() { - const string code = @" -function foo(] ) { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(15, 1)) - ), - CheckBlock( - CheckFunctionObject( - "foo", - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorFunctionExtraComma() { - const string code = @" -function foo(, ) { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(15, 1)) - ), - CheckBlock( - CheckFunctionObject( - "foo", - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorFunctionNoRightParen() { - const string code = @" -function foo( { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(16, 1)) - ), - CheckBlock( - CheckFunctionObject( - "foo", - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorFunctionNoRightParen2() { - const string code = @" -function foo(abc { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(19, 1)) - ), - CheckBlock( - CheckFunctionObject( - "foo", - CheckBlock(), - CheckParameterDeclaration("abc") - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestFunctionStrictMode() { - const string code = @" -function abc() { - 'use strict'; -}"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckFunctionObject( - "abc", - CheckBlock( - CheckExprStmt( - CheckDirectivePrologue("use strict", useStrict: true) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestFunctionKeywordIdentifier() { - const string code = @" -function get() { -}"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckFunctionObject( - "get", - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestGenerators() { - string code = @" -function *x() { - yield 1; -}"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckExprStmt( - CheckYieldExpr( - One - ) - ) - ), - true - ) - ) - ); - - code = @" -function *x() { - yield *1; -}"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckExprStmt( - CheckYieldExpr( - One, - true - ) - ) - ), - true - ) - ) - ); - - code = @" -function *x() { - yield 1 + 2; -}"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckExprStmt( - CheckYieldExpr( - CheckBinary( - JSToken.Plus, - One, - Two - ) - ) - ) - ), - true - ) - ) - ); - - } - - [TestMethod, Priority(0)] - public void TestYieldMember() { - string code = @" -function *x() { - abc.yield; -}"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckExprStmt( - CheckMember( - "yield", - CheckLookup("abc") - ) - ) - ), - true - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningYieldNoSemicolon() { - var code = @" -function *x() { - yield 4 4 -} -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(31, 1)) - ), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckExprStmt(CheckYieldExpr(Four)), - CheckExprStmt(Four) - ), - true - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorYieldVar() { - var code = @" -function *x() { - var yield; -} -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(27, 5)) - ), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckVar(CheckVarDecl("yield")) - ), - true - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorYieldLabel() { - var code = @" -function *x() { - while(true) { - continue yield; - } -} -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(55, 5)) - ), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckWhileStmt( - True, - CheckBlock( - CheckContinue(), - CheckExprStmt(CheckYieldExpr(null)) - ) - ) - ), - true - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningYieldSemiColonInsertion() { - var code = @" -function *x() { - yield 4 - 4 -} -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(30, 0)) - ), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckExprStmt(CheckYieldExpr(Four)), - CheckExprStmt(Four) - ), - true - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryYieldBadOperand() { - var code = @" -function *x() { - yield foo.'abc' -}"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(33, 5)) - ), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckExprStmt( - CheckYieldExpr( - CheckLookup("foo") - ) - ) - ), - true - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoverYieldBadOperand2() { - var code = @" -function *x() { - yield else -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(29, 4)) - ), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock( - CheckExprStmt(CheckYieldExpr(null)) - ), - true - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryYieldBadOperand3() { - var code = @" -function *x() { - yield else function -}"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(29, 4)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(44, 1)) - ), - CheckBlock( - CheckFunctionObject( - "x", - CheckBlock(CheckExprStmt(CheckYieldExpr(null))), - true - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorTryCatchNoTryOrFinally() { - const string code = @" -try { -} -foo"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoCatch, true, new IndexSpan(12, 3)) - ), - CheckBlock( - CheckTryFinally( - CheckBlock(), - CheckBlock() - ), - CheckLookupStmt("foo") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorTryCatchBadCatch() { - const string code = @" -try { -} catch(foo) { - else -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(29, 4)) - ), - CheckBlock( - CheckTryCatch( - CheckBlock(), - CheckParameterDeclaration("foo"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorTryCatchBadCatch2() { - const string code = @" -try { -} catch(foo) { - else function -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(29, 4)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(44, 1)) - ), - CheckBlock( - CheckBlock() - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorTryCatchNonIdentifierKeyword() { - const string code = @" -try { -} catch(function) { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(17, 8)), - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(25, 1)), - new ErrorInfo(JSError.ErrorEndOfFile, true, new IndexSpan(31, 0)), - new ErrorInfo(JSError.UnclosedBlock, true, new IndexSpan(17, 8)) - ), - CheckBlock( - CheckTryCatch( - CheckBlock(), - CheckParameterDeclaration("function"), - CheckBlock( - CheckFunctionObject(")", CheckBlock(), null) - ) - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void ErrorTryCatchIdentifierKeyword() { - const string code = @" -try { -} catch(get) { -}"; - CheckAst( - ParseCode( - code - ), - CheckBlock( - CheckTryCatch( - CheckBlock(), - CheckParameterDeclaration("get"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorTryCatchNoParens() { - const string code = @" -try { -} catch quox { -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(17, 4)), - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(22, 1)) - ), - CheckBlock( - CheckTryCatch( - CheckBlock(), - CheckParameterDeclaration("quox"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorTryBadBody() { - const string code = @" -try { - else -} catch(quox) { - foo -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(13, 4)) - ), - CheckBlock( - CheckTryCatch( - CheckBlock(), - CheckParameterDeclaration("quox"), - CheckBlock(CheckLookupStmt("foo")) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorTryBadBody2() { - const string code = @" -try { - else function -} catch(quox) { - foo -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(13, 4)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(28, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(54, 1)) - ), - CheckBlock( - CheckBlock(), - CheckLookupStmt("foo") - ) - ); - } - - - [TestMethod, Priority(0)] - public void ErrorTryMissingCurlys() { - const string code = @" -try - foo -catch(quox) - foo -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(11, 3)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(16, 5)), - new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(33, 3)) - ), - CheckBlock( - CheckTryCatch( - CheckBlock(CheckLookupStmt("foo")), - CheckParameterDeclaration("quox"), - CheckBlock(CheckLookupStmt("foo")) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningThrowSemicolonInsertion() { - const string code = @" -throw foo -foo"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(11, 0)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(8, 3)) - ), - CheckBlock( - CheckThrow( - CheckLookup("foo") - ), - CheckLookupStmt("foo") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorThrowNoSemicolon() { - const string code = @" -throw foo foo"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(12, 3)) - ), - CheckBlock( - CheckThrow( - CheckLookup("foo") - ), - CheckLookupStmt("foo") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorThrowBadExpression2() { - const string code = @" -throw foo.'abc' foo"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(12, 5)) - ), - CheckBlock( - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorThrowBadExpression() { - const string code = @" -throw foo.'abc' -foo"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(12, 5)) - ), - CheckBlock( - CheckThrow( - CheckLookup("foo") - ), - CheckLookupStmt("foo") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchCaseBadStatement() { - const string code = @" -switch(foo){ - case 0: - else -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(37, 4)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("foo"), - CheckCase( - Zero, - CheckBlock() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchCaseBadCase2() { - const string code = @" -switch(foo){ - case foo.'abc' else function -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(29, 5)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(50, 1)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("foo") - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchCaseBadCase() { - const string code = @" -switch(foo){ - case foo.'abc': -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(29, 5)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("foo"), - CheckCase( - CheckLookup("foo"), - CheckBlock() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchCaseMissingColon() { - const string code = @" -switch(foo){ - case 0 -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoColon, true, new IndexSpan(28, 1)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("foo"), - CheckCase( - Zero, - CheckBlock() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchNoCasesOrDefault() { - const string code = @" -switch(foo){ - foo -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.BadSwitch, true, new IndexSpan(20, 3)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("foo"), - CheckCase( - null, - CheckBlock( - CheckLookupStmt("foo") - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchDuplicateDefault() { - const string code = @" -switch(foo){ - default: - default: -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.DupDefault, true, new IndexSpan(34, 7)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("foo"), - CheckCase( - null, - CheckBlock() - ), - CheckCase( - null, - CheckBlock() - ) - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void ErrorSwitchBadExpressionNoLeftCurly() { - const string code = @" -switch(foo.'abc') - case 0: -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(13, 5)), - new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(26, 4)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("foo"), - CheckCase( - Zero, - CheckBlock() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchBadExpression() { - const string code = @" -switch(foo.'abc') { - case 0: -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(13, 5)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("foo"), - CheckCase( - Zero, - CheckBlock() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchBadExpression2() { - const string code = @" -switch(else) { - case 0: -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(9, 4)) - ), - CheckBlock( - CheckSwitch( - CheckConstant(true), - CheckCase( - Zero, - CheckBlock() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchBadExpression3() { - const string code = @" -switch(else function) { - case 0: -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(9, 4)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(22, 1)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(31, 4)) - ), - CheckBlock( - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchNoLeftCurly() { - const string code = @" -switch(abc) - case 0: -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(20, 4)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("abc"), - CheckCase( - Zero, - CheckBlock() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorSwitchNoParens() { - const string code = @" -switch abc { - case 0: -}"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(9, 3)), - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(13, 1)) - ), - CheckBlock( - CheckSwitch( - CheckLookup("abc"), - CheckCase( - Zero, - CheckBlock() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorBracketInvalidExpression() { - const string code = @" -i[foo.'abc'];"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(8, 5)) - ), - CheckBlock( - CheckExprStmt( - CheckIndex( - I, - CheckLookup("foo") - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorBracketInvalidExpression2() { - const string code = @" -i[foo.'abc' function];"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(8, 5)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(22, 1)) - ), - CheckBlock( - CheckExprStmt( - CheckIndex( - I, - CheckLookup("foo") - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorBracketInvalidExpression3() { - const string code = @" -i[else function];"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(4, 4)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(17, 1)) - ), - CheckBlock( - CheckExprStmt( - I - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorBracketInvalidExpression4() { - const string code = @" -i[function];"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(13, 1)), - new ErrorInfo(JSError.ErrorEndOfFile, true, new IndexSpan(14, 0)), - new ErrorInfo(JSError.UnclosedFunction, true, new IndexSpan(4, 8)) - ), - CheckBlock( - CheckExprStmt( - CheckIndex( - I, - CheckFunctionExpr( - CheckFunctionObject( - null, - CheckBlock(), - null - ) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningOctalLiteral() { - const string code = @" -i = 0100;"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.OctalLiteralsDeprecated, false, new IndexSpan(6, 4)), - new ErrorInfo(JSError.OctalLiteralsDeprecated, false, new IndexSpan(6, 4)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(2, 1)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckConstant(64.0) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorObjectLiteralNoColon() { - const string code = @" -i = {abc:42, foo 0};"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoColon, true, new IndexSpan(19, 1)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckObjectLiteral( - Property("abc", CheckConstant(42.0)), - Property("foo", Zero) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorObjectLiteralNoRightCurly() { - const string code = @" -i = {abc:42, foo:0 -foo -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoRightCurly, true, new IndexSpan(22, 3)) - ), - CheckBlock( - CheckLookupStmt("i"), - CheckLookupStmt("foo") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorObjectLiteralNoComma() { - const string code = @" -i = {abc:42, foo:0 foo -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoComma, true, new IndexSpan(21, 3)), - new ErrorInfo(JSError.UnclosedObjectLiteral, true, new IndexSpan(6, 1)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckObjectLiteral( - Property("abc", CheckConstant(42.0)), - Property("foo", Zero) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorObjectLiteralBadExpression() { - const string code = @" -i = {abc:foo.'abc'};"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckObjectLiteral( - Property("abc", CheckLookup("foo")) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorObjectLiteralBadExpression2() { - const string code = @" -i = {abc:foo.'abc' else };"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckObjectLiteral( - Property("abc", CheckLookup("foo")) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorObjectLiteralBadExpression3() { - const string code = @" -i = {abc:foo.'abc' else, foo:42 };"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckObjectLiteral( - Property("abc", CheckLookup("foo")), - Property("foo", CheckConstant(42.0)) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorObjectLiteralNoMemberIdentifier() { - const string code = @" -i = {abc:foo, [:42};"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoMemberIdentifier, true, new IndexSpan(16, 1)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckObjectLiteral( - Property("abc", CheckLookup("foo")), - Property("[", CheckConstant(42.0)) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorObjectLiteralInvalidNumeric() { - const string code = @" -i = {abc:foo, 1e100000:42};"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NumericOverflow, true, new IndexSpan(16, 8)) - ), - CheckBlock( - CheckExprStmt( - CheckAssign( - I, - CheckObjectLiteral( - Property("abc", CheckLookup("foo")), - Property(new InvalidNumericErrorValue("1e100000"), CheckConstant(42.0)) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void VariableDeclarationBadInitializer2() { - const string code = @" -var i = foo.'abc' else function;"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(14, 5)) - ), - CheckBlock( - CheckVar(CheckVarDecl("i", CheckLookup("foo"))), - CheckEmptyStmt() - ) - ); - } - - /// - /// https://nodejstools.codeplex.com/workitem/1227 - /// - /// Verify that when we report the missing semicolon error that we properly - /// don't treat the current token twice. - /// - [TestMethod, Priority(0)] - public void InvalidTrailingStringLiteral() { - var preceedingTests = new[] { - new { Code = "var i = 0", Expected = CheckVar(CheckVarDecl("i", Zero)) }, - new { Code = "break", Expected = CheckBreak() }, - new { Code = "continue", Expected = CheckContinue() }, - new { Code = "return 0", Expected = CheckReturn(Zero) }, - new { Code = "throw 0", Expected = CheckThrow(Zero) }, - }; - foreach (var test in preceedingTests) { - Console.WriteLine(test.Code); - string code = test.Code + "'"; - CheckAst( - ParseCode( - code, - null - ), - CheckBlock( - test.Expected, - CheckConstantStmt("") - ) - ); - } - } - - [TestMethod, Priority(0)] - public void VariableDeclarationBadInitializer3() { - const string code = @" -var j = 0, i = foo.'abc' else function;"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(21, 5)) - ), - CheckBlock( - CheckVar( - CheckVarDecl("j", Zero), - CheckVarDecl("i", CheckLookup("foo")) - ), - CheckEmptyStmt() - ) - ); - } - - [TestMethod, Priority(0)] - public void VariableDeclarationBadInitializer() { - const string code = @" -var i = foo.'abc';"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(14, 5)) - ), - CheckBlock( - CheckVar( - CheckVarDecl("i", CheckLookup("foo")) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void VariableDeclarationEqual() { - const string code = @" -var i == 1;"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoEqual, true, new IndexSpan(8, 2)) - ), - CheckBlock( - CheckVar( - CheckVarDecl("i", One) - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void ErrorDebuggerMissingSemiColon() { - const string code = @" -debugger foo"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(11, 3)) - ), - CheckBlock( - CheckDebuggerStmt(), - CheckLookupStmt("foo") - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningDebuggerMissingSemiColon() { - const string code = @" -debugger -foo"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(10, 0)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(12, 3)) - ), - CheckBlock( - CheckDebuggerStmt(), - CheckLookupStmt("foo") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorDuplicateLabel() { - const string code = @" -label: -label: -blah; -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.BadLabel, true, new IndexSpan(10, 5)) - ), - CheckBlock( - CheckLabel( - "label", - CheckBlock() - ), - CheckLookupStmt("blah") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorBadContinueBadLabel() { - const string code = @" -foo: -continue foo -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.BadContinue, true, new IndexSpan(8, 12)) - ), - CheckBlock( - CheckLabel( - "foo", - CheckContinue("foo") - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningContinueSemicolonInsertion() { - const string code = @" -for(var i = 0; i<4; i++) { - continue - blah -} -"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(42, 0)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(48, 4)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckContinue(), - CheckLookupStmt("blah") - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningBreakSemicolonInsertion() { - const string code = @" -for(var i = 0; i<4; i++) { - break - blah -} -"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(39, 0)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(45, 4)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckBreak(), - CheckLookupStmt("blah") - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorContinueNoSemicolon() { - const string code = @" -for(var i = 0; i<4; i++) { - continue if(true) { } -} -"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(43, 2)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckContinue(), - CheckIfStmt(True, CheckBlock()) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorBreakNoSemicolon() { - const string code = @" -for(var i = 0; i<4; i++) { - break if(true) { } -} -"; - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(40, 2)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckBreak(), - CheckIfStmt(True, CheckBlock()) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorBadContinue() { - const string code = @" -continue -blah -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.BadContinue, true, new IndexSpan(2, 8)) - ), - CheckBlock( - CheckContinue(), - CheckLookupStmt("blah") - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorBadBreak() { - const string code = @" -break -blah -"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.BadBreak, true, new IndexSpan(2, 5)) - ), - CheckBlock( - CheckBreak(), - CheckLookupStmt("blah") - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningWithNoParens() { - var code = @" -with abc { -} -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(7, 3)), - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(11, 1)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(7, 3)) - ), - CheckBlock( - CheckWith( - CheckLookup("abc"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningReturnNoSemicolon() { - var code = @" -return 4 4 -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(11, 1)) - ), - CheckBlock( - CheckReturn(Four), - CheckExprStmt(Four) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningReturnSemiColonInsertion() { - var code = @" -return 4 -4 -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(10, 0)) - ), - CheckBlock( - CheckReturn(Four), - CheckExprStmt(Four) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryReturnBadOperand() { - var code = @" -return foo.'abc' - -{}"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(13, 5)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(23, 1)) - ), - CheckBlock( - CheckReturn( - CheckLookup("foo") - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoverReturnBadOperand2() { - var code = @" -return else - -{} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(9, 4)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(18, 1)) - ), - CheckBlock( - CheckReturn() - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryReturnBadOperand3() { - var code = @" -return else function"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(9, 4)) - ), - CheckBlock( - CheckReturn() - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorIfMissingParens() { - var code = @" -if true { -}"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(5, 4)), - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(10, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningIfSuspectAssign() { - var code = @" -if(i = 1) { -}"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SuspectAssignment, false, new IndexSpan(5, 5)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(5, 1)) - ), - CheckBlock( - CheckIfStmt( - CheckAssign(I, One), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningIfSuspectSemicolon() { - var code = @" -if(true);"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SuspectSemicolon, false, new IndexSpan(10, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - CheckEmptyStmt() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningIfElseSuspectSemicolon() { - var code = @" -if(true) { } -else;"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SuspectSemicolon, false, new IndexSpan(20, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - ), - CheckBlock( - CheckEmptyStmt() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningIfElseStatementBlock() { - var code = @" -if(true) { } -else i"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.StatementBlockExpected, false, new IndexSpan(21, 0)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(21, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - ), - CheckBlock( - CheckExprStmt(I) - ) - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void RecoveryIfBadConditional() { - var code = @" -if(foo.'abc') { -}"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(9, 5)) - ), - CheckBlock( - CheckIfStmt( - CheckLookup("foo"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryIfBadConditional2() { - var code = @" -if(else) { -}"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(5, 4)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryIfBadConditional3() { - var code = @" -if(else function) { -}"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(5, 4)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(18, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(23, 1)) - ), - CheckBlock( - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryIfBadBody() { - var code = @" -if(true) - foo.'abc' - -{ }"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(21, 5)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(32, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - CheckExprStmt(CheckLookup("foo")) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryIfBadBody2() { - var code = @" -if(true) - } - -{ }"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(17, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(24, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryIfBadBody3() { - var code = @" -if(true) - } function - -{ }"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(17, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - ) - ), - CheckBlock() - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryIfElseBadBody() { - var code = @" -if(true) { -} else - foo.'abc' - -{ }"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(30, 5)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(41, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - ), - CheckBlock( - CheckExprStmt(CheckLookup("foo")) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryIfElseBadBody2() { - var code = @" -if(true) { -} else - } - -{ }"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(27, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(34, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - ), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryIfElseBadBody3() { - var code = @" -if(true) { -} else - } function - -{ }"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(26, 1)) - ), - CheckBlock( - CheckIfStmt( - True, - CheckBlock( - ), - CheckBlock() - ), - CheckBlock() - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningWhileSuspectAssignment() { - var code = @" -while(i = 4) { -} -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SuspectAssignment, false, new IndexSpan(8, 5)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(8, 1)) - ), - CheckBlock( - CheckWhileStmt( - CheckAssign(I, Four), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryWhileBadBody() { - var code = @" -while(true) - foo.'abc' - -{ -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(24, 5)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(36, 1)) - ), - CheckBlock( - CheckWhileStmt( - True, - CheckBlock( - CheckExprStmt(CheckLookup("foo")) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryWhileBadBody2() { - var code = @" -while(true) - else - -{ } -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(20, 4)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(30, 1)) - ), - CheckBlock( - CheckWhileStmt( - True, - CheckBlock( - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryWhileBadCondition() { - var code = @" -while(foo.'abc') { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(12, 5)) - ), - CheckBlock( - CheckWhileStmt( - CheckLookup("foo"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryWhileBadCondition2() { - var code = @" -while(else) { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(8, 4)) - ), - CheckBlock( - CheckWhileStmt( - False, - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryWhileBadCondition3() { - var code = @" -while(else function) { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(8, 4)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(21, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(26, 1)) - ), - CheckBlock( - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorWhileNoOpenParens() { - var code = @" -while true { -} -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(8, 4)), - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(13, 1)) - ), - CheckBlock( - CheckWhileStmt( - True, - CheckBlock() - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void RecoveryDoWhileBadCondition() { - var code = @" -do { - true -} while(foo.'abc'); -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(30, 5)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock( - CheckExprStmt(True) - ), - CheckLookup("foo") - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryDoWhileBadCondition2() { - var code = @" -do { - true -} while(< function() { }); -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(26, 1)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(37, 1)), - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(39, 1)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(42, 1)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock( - CheckExprStmt(True) - ), - CheckConstant(false) - ), - CheckExprStmt(CheckGrouping(IsNullExpr)), - CheckBlock() - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorDoWhileMissngParens() { - var code = @" -do { - true -} while true; -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(26, 4)), - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(30, 1)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock( - CheckExprStmt(True) - ), - True - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void ErrorDoWhileNoWhile() { - var code = @" -do { - true -} (true); -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.NoWhile, true, new IndexSpan(20, 1)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock( - CheckExprStmt(True) - ), - True - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void WarningDoWhileMissingOpenCurly() { - var code = @" -do - true -while(true); -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.StatementBlockExpected, false, new IndexSpan(11, 0)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock( - CheckExprStmt(True) - ), - True - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningDoWhileSuspectAssignment() { - var code = @" -do { - true -} while(i = 4); -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SuspectAssignment, false, new IndexSpan(26, 5)), - new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(26, 1)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock( - CheckExprStmt(True) - ), - CheckAssign( - I, - Four - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryDoWhileBadBody() { - var code = @" -do - foo.'abc' -while(true); -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock( - CheckExprStmt(CheckLookup("foo")) - ), - True - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryDoWhileBadBody2() { - var code = @" -do - else -while(true); -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(10, 4)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock(), - True - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryDoWhileBadBody3() { - var code = @" -do - else function -while(true); -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(10, 4)) - ), - CheckBlock( - CheckDoWhileStmt( - CheckBlock(), - False - ), - CheckWhileStmt( - True, - CheckBlock( - CheckEmptyStmt() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningForAssignment() { - var code = @" -for(var i = 0; i = 4; i++) { -} -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.SuspectAssignment, false, new IndexSpan(17, 5)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckAssign(I, Four), - CheckIncrement(I), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WarningForStatementBlock() { - var code = @" -for(var i = 0; i < 4; i++) - i++; -"; - - CheckAst( - ParseCode( - code, - true, - new ErrorInfo(JSError.StatementBlockExpected, false, new IndexSpan(35, 0)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckExprStmt( - CheckIncrement(I) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForIn() { - var code = @" -for(x in ) { -} - -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(11, 1)) - ), - CheckBlock( - CheckForInStmt( - CheckExprStmt(CheckLookup("x")), - CheckConstant(true), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForInBadCollection() { - var code = @" -for(x in foo.'abc') { -} - -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) - ), - CheckBlock( - CheckForInStmt( - CheckExprStmt(CheckLookup("x")), - CheckLookup("foo"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForIn2() { - var code = @" -for(x in < function() { }) { -} - -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(11, 1)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(22, 1)), - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(24, 1)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(27, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(32, 1)) - ), - CheckBlock( - CheckExprStmt(CheckGrouping(IsNullExpr)), - CheckBlock() - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForInBadBody() { - // foo.'abc' will trigger a recovery w/ an expression which can be - // promoted to an expression statement - var code = @" -for(x in abc) - foo.'abc' - -{ -} - -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(26, 5)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(38, 1)) - ), - CheckBlock( - CheckForInStmt( - CheckExprStmt(CheckLookup("x")), - CheckLookup("abc"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForInBadBody2() { - // } will trigger a recovery w/ an expression - var code = @" -for(x in abc) - } - -{ -} - -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(22, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(30, 1)) - ), - CheckBlock( - CheckForInStmt( - CheckExprStmt(CheckLookup("x")), - CheckLookup("abc"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForNoClosingParen() { - // } will trigger a recovery w/ an expression - var code = @" -for(x in abc { -} - -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(15, 1)) - ), - CheckBlock( - CheckForInStmt( - CheckExprStmt(CheckLookup("x")), - CheckLookup("abc"), - CheckBlock() - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void RecoveryForBadBody() { - // foo.'abc' will trigger a recovery w/ an expression which can be - // promoted to an expression statement - var code = @" -for(var i = 0; i< 4; i++) - foo.'abc' - -{ -} - -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(38, 5)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(50, 1)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckExprStmt( - CheckLookup("foo") - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForBadBody2() { - // } will trigger a recovery w/ an expression - var code = @" -for(var i = 0; i< 4; i++) - } - -{ -} - -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(34, 1)), - new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(42, 1)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - ) - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void RecoveryForNoOpenParen() { - var code = @" -for var i = 0; i<4; i++) { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(6, 3)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForMissingClosingParen() { - var code = @" -for(var i = 0; i<4; i++ { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(26, 1)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForMissingIncrement() { - var code = @" -for(var i = 0; i<4) { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(20, 1)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - IsNullExpr, - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForMissingConditionColon() { - var code = @" -for(var i = 0 : ) { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(16, 1)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckConstant(true), - IsNullExpr, - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForMissingConditionColonSemicolon() { - var code = @" -for(var i = 0 : ; i < 4) { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(16, 1)), - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(25, 1)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - IsNullExpr, - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForUnknownFollowingToken() { - var code = @" -for(var i = 0, j = 0; < function() { } { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(24, 1)), - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(35, 1)), - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(37, 1)) - ), - CheckBlock( - CheckExprStmt(CheckGrouping(IsNullExpr)), - CheckBlock(), - CheckBlock() - ) - ); - } - - [TestMethod, Priority(0)] - public void RecoveryForMissingConditionMultipleVars() { - var code = @" -for(var i = 0, j = 0) { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(22, 1)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero), CheckVarDecl("j", Zero)), - CheckConstant(true), - IsNullExpr, - CheckBlock() - ) - ) - ); - } - - /// - /// let not in strict mode should be fine... - /// - [TestMethod, Priority(0)] - public void RecoveryForMissingCondition() { - var code = @" -for(var i = 0;) { -} -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(16, 1)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckConstant(true), - IsNullExpr, - CheckBlock() - ) - ) - ); - - - - code = @" -for(var i = 0; -"; - - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(18, 0)) - ), - CheckBlock( - ) - ); - } - - /// - /// let not in strict mode should be fine... - /// - [TestMethod, Priority(0)] - public void ForMultipleVariables() { - var code = @" -for(var i = 0, j = 1; i<4; i++) { -} -"; - - CheckAst( - ParseCode(code), - CheckBlock( - CheckForStmt( - CheckVar( - CheckVarDecl("i", Zero), - CheckVarDecl("j", One) - ), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock() - ) - ) - ); - } - - /// - /// let not in strict mode should be fine... - /// - [TestMethod, Priority(0)] - public void LetNotInStrictMode() { - var code = @" - let = _(let).succ(); -"; - - CheckAst( - ParseCode(code), - CheckBlock( - CheckBinaryStmt( - JSToken.Assign, - CheckLookup("let"), - CheckCall( - CheckMember( - "succ", - CheckCall( - CheckLookup("_"), - CheckLookup("let") - ) - ) - ) - ) - ) - ); - } - - /// - - /// IdentifierName :: - /// IdentifierStart - /// IdentifierName IdentifierPart - /// IdentifierStart :: - /// UnicodeLetter - /// $ - /// _ - /// \ UnicodeEscapeSequence - ///IdentifierPart :: - /// IdentifierStart - /// UnicodeCombiningMark - /// UnicodeDigit - /// UnicodeConnectorPunctuation - /// \u200C (ZWNJ) - /// \u200D (ZWJ) - /// - [TestMethod, Priority(0)] - public void IdentifierNames() { - const string identifiers = @" -$ -$f -_ -_f -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckLookupStmt("$"), - CheckLookupStmt("$f"), - CheckLookupStmt("_"), - CheckLookupStmt("_f") - ) - ); - } - - [TestMethod, Priority(0)] - public void TestUnicodeIdentifier() { - const string identifiers = @" -\u1234abc -\u1234\u1235abc -abc\u1234 -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckLookupStmt("\u1234abc"), - CheckLookupStmt("\u1234\u1235abc"), - CheckLookupStmt("abc\u1234") - ) - ); - } - - [TestMethod, Priority(0)] - public void TestBlock() { - const string code = @" -{ 1; 2 }"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckBlock( - CheckConstantStmt(One), - CheckConstantStmt(Two) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestDebugger() { - const string code = @" -debugger;"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckDebuggerStmt() - ) - ); - } - - [TestMethod, Priority(0)] - public void TestSwitch() { - const string code = @" -switch(abc) { - case 0: - case ""abc"": - 1; - break; - default: - 2; - break; -}"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckSwitch( - CheckLookup("abc"), - CheckCase( - Zero, - CheckBlock() - ), - CheckCase( - CheckConstant("abc"), - CheckBlock( - CheckConstantStmt(1.0), - CheckBreak() - ) - ), - CheckCase( - null, - CheckBlock( - CheckConstantStmt(2.0), - CheckBreak() - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestThrow() { - const string code = @" -throw ""abc""; -throw; -"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckThrow(CheckConstant("abc")), - CheckThrow() - ) - ); - } - - [TestMethod, Priority(0)] - public void TestWith() { - const string code = @" -with(abc) { - 1; -} -"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckWith( - CheckLookup("abc"), - CheckBlock( - CheckConstantStmt(1.0) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestConditional() { - const string identifiers = @" -1 ? 2 : 3 -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckExprStmt( - CheckConditional( - One, - Two, - Three - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestVoid() { - const string identifiers = @" -void 1 -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckExprStmt( - CheckUnary( - JSToken.Void, - One - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestTypeOf() { - const string identifiers = @" -typeof 1 -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckExprStmt( - CheckUnary( - JSToken.TypeOf, - One - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestUnary() { - const string identifiers = @" -+1; --1; -~1; -!1; -delete abc; -++abc; ---abc; -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckUnaryStmt(JSToken.Plus, One), - CheckUnaryStmt(JSToken.Minus, One), - CheckUnaryStmt(JSToken.BitwiseNot, One), - CheckUnaryStmt(JSToken.LogicalNot, One), - CheckUnaryStmt(JSToken.Delete, CheckLookup("abc")), - CheckUnaryStmt(JSToken.Increment, CheckLookup("abc")), - CheckUnaryStmt(JSToken.Decrement, CheckLookup("abc")) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestUnaryPostFix() { - const string identifiers = @" -abc++; -abc--; -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckUnaryStmt(JSToken.Increment, CheckLookup("abc"), isPostFix: true), - CheckUnaryStmt(JSToken.Decrement, CheckLookup("abc"), isPostFix: true) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestGrouping() { - const string identifiers = @" -(1) -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckExprStmt(CheckGrouping(One)) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestArrayLiteral() { - const string identifiers = @" -[1,2,3]; -[1,2,3,] -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckExprStmt(CheckArrayLiteral(One, Two, Three)), - CheckExprStmt(CheckArrayLiteral(One, Two, Three)) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestObjectLiteral() { - // Expression statements can't start with { as it's ambiguous with block, - // so we do an assignment here - const string identifiers = @" -x = {'abc':1} -x = {'abc':1,} -x = {abc:1} -x = {42:1,} -x = {get abc () { 42 }} -x = {set abc (value) { 42 }} -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckBinaryStmt( - JSToken.Assign, - CheckLookup("x"), - CheckObjectLiteral(Property("abc", One)) - ), - CheckBinaryStmt( - JSToken.Assign, - CheckLookup("x"), - CheckObjectLiteral(Property("abc", One)) - ), - CheckBinaryStmt( - JSToken.Assign, - CheckLookup("x"), - CheckObjectLiteral(Property("abc", One)) - ), - CheckBinaryStmt( - JSToken.Assign, - CheckLookup("x"), - CheckObjectLiteral(Property(42.0, One)) - ), - CheckBinaryStmt( - JSToken.Assign, - CheckLookup("abc"), - CheckObjectLiteral( - GetterSetter( - true, - "abc", - CheckFunctionExpr( - CheckFunctionObject( - null, - CheckBlock() - ) - ) - ) - ) - ), - CheckBinaryStmt( - JSToken.Assign, - CheckLookup("abc"), - CheckObjectLiteral( - GetterSetter( - false, - "abc", - CheckFunctionExpr( - CheckFunctionObject( - null, - CheckBlock(), - CheckParameterDeclaration("value") - ) - ) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestPrecedence() { - const string code = @" -1 + 2 * 3; -1 + 2 / 3; -1 + 2 % 3; -1 - 2 * 3; -1 - 2 / 3; -1 - 2 % 3; -1 << 2 + 3; -1 << 2 - 3; -1 >> 2 + 3; -1 >> 2 - 3; -1 >>> 2 + 3; -1 >>> 2 - 3; -1 < 2 >> 3; -1 > 2 >> 3; -1 <= 2 >> 3; -1 >= 2 >> 3; -1 instanceof 2 >> 3; -1 in 2 >> 3; -1 == 2 < 3; -1 != 2 < 3; -1 === 2 < 3; -1 !== 2 < 3; -1 & 2 == 3; -1 ^ 2 & 3; -1 | 2 ^ 3; -1 && 2 | 3 -1 || 2 && 3 -1 ? 2 : 3 || 4 -"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckPrecedence(JSToken.Plus, JSToken.Multiply), - CheckPrecedence(JSToken.Plus, JSToken.Divide), - CheckPrecedence(JSToken.Plus, JSToken.Modulo), - CheckPrecedence(JSToken.Minus, JSToken.Multiply), - CheckPrecedence(JSToken.Minus, JSToken.Divide), - CheckPrecedence(JSToken.Minus, JSToken.Modulo), - CheckPrecedence(JSToken.LeftShift, JSToken.Plus), - CheckPrecedence(JSToken.LeftShift, JSToken.Minus), - CheckPrecedence(JSToken.RightShift, JSToken.Plus), - CheckPrecedence(JSToken.RightShift, JSToken.Minus), - CheckPrecedence(JSToken.UnsignedRightShift, JSToken.Plus), - CheckPrecedence(JSToken.UnsignedRightShift, JSToken.Minus), - CheckPrecedence(JSToken.LessThan, JSToken.RightShift), - CheckPrecedence(JSToken.GreaterThan, JSToken.RightShift), - CheckPrecedence(JSToken.LessThanEqual, JSToken.RightShift), - CheckPrecedence(JSToken.GreaterThanEqual, JSToken.RightShift), - CheckPrecedence(JSToken.InstanceOf, JSToken.RightShift), - CheckPrecedence(JSToken.In, JSToken.RightShift), - CheckPrecedence(JSToken.Equal, JSToken.LessThan), - CheckPrecedence(JSToken.NotEqual, JSToken.LessThan), - CheckPrecedence(JSToken.StrictEqual, JSToken.LessThan), - CheckPrecedence(JSToken.StrictNotEqual, JSToken.LessThan), - CheckPrecedence(JSToken.BitwiseAnd, JSToken.Equal), - CheckPrecedence(JSToken.BitwiseXor, JSToken.BitwiseAnd), - CheckPrecedence(JSToken.BitwiseOr, JSToken.BitwiseXor), - CheckPrecedence(JSToken.LogicalAnd, JSToken.BitwiseOr), - CheckPrecedence(JSToken.LogicalOr, JSToken.BitwiseAnd), - CheckExprStmt( - CheckConditional( - One, - Two, - CheckBinary(JSToken.LogicalOr, Three, Four) - ) - ) - ) - ); - } - - private static Action CheckPrecedence(JSToken lower, JSToken higher) { - return CheckBinaryStmt( - lower, - One, - CheckBinary( - higher, - Two, - Three - ) - ); - } - - /// - /// NullLiteral - /// null - /// BooleanLiteral - /// true false - /// - [TestMethod, Priority(0)] - public void SimpleLiterals() { - const string identifiers = @" -null -true -false -"; - CheckAst( - ParseCode(identifiers), - CheckBlock( - CheckConstantStmt(null), - CheckConstantStmt(true), - CheckConstantStmt(false) - ) - ); - } - - /// - /// NumericLiteral - /// DecimalLiteral - /// HexIntegerLiteral - /// - /// DecimalLiteral :: - /// DecimalIntegerLiteral . DecimalDigitsopt ExponentPartopt - /// . DecimalDigits ExponentPartopt - /// DecimalIntegerLiteral ExponentPartopt - /// DecimalIntegerLiteral :: - /// 0 - /// NonZeroDigit DecimalDigitsopt - /// DecimalDigits :: - /// DecimalDigit - /// DecimalDigits DecimalDigit - /// DecimalDigit :: one of - /// 0 1 2 3 4 5 6 7 8 9 - /// NonZeroDigit :: one of - /// 1 2 3 4 5 6 7 8 9 - /// ExponentPart :: - /// ExponentIndicator SignedInteger - /// ExponentIndicator :: one of - /// e E - /// SignedInteger :: - /// DecimalDigits - /// + DecimalDigits - /// - DecimalDigits - /// HexIntegerLiteral :: - /// 0x HexDigit - /// 0X HexDigit - /// HexIntegerLiteral HexDigit - /// HexDigit :: one of - /// 0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F - /// - [Ignore] - [TestMethod, Priority(0)] - public void TestNumericLiterals() { - const string numbers = @" -0 -1 -2 -3 -4 -5 -6 -7 -8 -9 -0.0 -1.1 -10 -0e1 -0e+1 -0e-1 -0x0 -0X0 -0x1 -0x2 -0x3 -0x4 -0x5 -0x6 -0x7 -0x8 -0x9 -0xA -0xB -0xC -0xD -0xE -0xF -0x10 -"; - CheckAst( - ParseCode(numbers), - CheckBlock( - CheckConstantStmts( - 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0, - 1.1, 10.0, 0e1, 0e+1, 0e-1, 0.0, 0.0, 1.0, 2.0, 3.0, - 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, - 13.0, 14.0, 15.0, 16.0 - ) - ) - ); - } - - /// - /// StringLiteral :: - /// " DoubleStringCharactersopt " - /// ' SingleStringCharactersopt ' - /// DoubleStringCharacters :: - /// DoubleStringCharacter DoubleStringCharactersopt - /// SingleStringCharacters :: - /// SingleStringCharacter SingleStringCharactersopt - /// DoubleStringCharacter :: - /// SourceCharacter but not one of " or \ or LineTerminator - /// \ EscapeSequence - /// LineContinuation - /// SingleStringCharacter :: - /// SourceCharacter but not one of ' or \ or LineTerminator - /// \ EscapeSequence - /// LineContinuation - /// LineContinuation :: - /// \ LineTerminatorSequence - /// EscapeSequence :: - /// CharacterEscapeSequence - /// 0 [lookahead ∉ DecimalDigit] - /// HexEscapeSequence - /// UnicodeEscapeSequence - /// CharacterEscapeSequence :: - /// SingleEscapeCharacter - /// NonEscapeCharacter - /// SingleEscapeCharacter :: one of - /// ' " \ b f n r t v - /// NonEscapeCharacter :: - /// SourceCharacter but not one of EscapeCharacter or LineTerminator - /// EscapeCharacter :: - /// SingleEscapeCharacter - /// DecimalDigit - /// x - /// u - /// HexEscapeSequence :: - /// x HexDigit HexDigit - /// UnicodeEscapeSequence :: - /// u HexDigit HexDigit HexDigit HexDigit - /// - [TestMethod, Priority(0)] - public void TestStringLiterals() { - const string strings = @" -""hello"" -'hello' -""hel\ -lo"" -""\"""" -'\'' -'\\' -""\\"" -'\b' -'\f' -'\n' -'\r' -'\t' -'\v' -'\x42' -'\u0042' -'\Z' -"; - CheckAst( - ParseCode(strings), - CheckBlock( - CheckConstantStmts( - "hello", "hello", "hello", "\"", "'", - "\\", "\\", - "\b", "\f", "\n", "\r", "\t", "\v", - "\x42", "\u0042", - "Z" - ) - ) - ); - } - - /// - /// RegularExpressionLiteral :: - /// / RegularExpressionBody / RegularExpressionFlags - /// RegularExpressionBody :: - /// RegularExpressionFirstChar RegularExpressionChars - /// RegularExpressionChars :: - /// [empty] - /// RegularExpressionChars RegularExpressionChar - /// RegularExpressionFirstChar :: - /// RegularExpressionNonTerminator but not one of * or \ or / or [ - /// RegularExpressionBackslashSequence - /// RegularExpressionClass - /// RegularExpressionChar :: - /// RegularExpressionNonTerminator but not one of \ or / or [ - /// RegularExpressionBackslashSequence - /// RegularExpressionClass - /// RegularExpressionBackslashSequence :: - /// \ RegularExpressionNonTerminator - /// RegularExpressionNonTerminator :: - /// SourceCharacter but not LineTerminator - /// RegularExpressionClass :: - /// [ RegularExpressionClassChars ] - /// RegularExpressionClassChars :: - /// [empty] - /// RegularExpressionClassChars RegularExpressionClassChar - /// RegularExpressionClassChar :: - /// RegularExpressionNonTerminator but not one of ] or \ - /// RegularExpressionBackslashSequence - /// RegularExpressionFlags :: - /// [empty] - /// RegularExpressionFlags IdentifierPart - /// - [TestMethod, Priority(0)] - public void TestRegexLiterals() { - // TODO: More test cases - const string regexs = @" -/abc/; -/abc/i; -/[abc]/; -/(?:)/ -"; - CheckAst( - ParseCode(regexs), - CheckBlock( - CheckRegExs( - "abc", - new RegEx("abc", "i"), - "[abc]", - "(?:)" - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestVariableDeclaration() { - const string code = @" -var i = 0, j = 1; -"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckVar( - CheckVarDecl("i", Zero), - CheckVarDecl("j", One) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestVariableDeclarationKeyword() { - const string code = @" -var get = 0;"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckVar( - CheckVarDecl("get", Zero) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestVariableDeclarationError() { - const string code = @" -var function = 0;"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(6, 8)) - ), - CheckBlock( - CheckVar( - CheckVarDecl("function", Zero) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestVariableDeclarationError2() { - const string code = @" -var 42 = 0;"; - CheckAst( - ParseCode( - code, - new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(6, 2)) - ), - CheckBlock( - CheckVar(), - CheckExprStmt(CheckConstant(42.0)), - CheckExprStmt(CheckAssign(CheckConstant(42.0), Zero)) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestEmptyStatement() { - const string code = @" -; -"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckEmptyStmt() - ) - ); - } - - [TestMethod, Priority(0)] - public void TestIfStatement() { - const string code = @" -if(true) { - 1 -}else{ - 2 -} - -if(true) { - 1 -} - -"; - CheckAst( - ParseCode(code), - CheckBlock( - CheckIfStmt( - True, - CheckBlock(CheckConstantStmt(One)), - CheckBlock(CheckConstantStmt(Two)) - ), - CheckIfStmt( - True, - CheckBlock(CheckConstantStmt(One)) - ) - ) - ); - } - - - [TestMethod, Priority(0)] - public void TestForStatement() { - const string ForCode = @" -for(var i = 0; i<4; i++) { } -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForStatementLexical() { - const string ForCode = @" -for(const i = 0; false; ) { } -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckForStmt( - CheckLexicalDecl(CheckVarDecl("i", Zero)), - CheckConstant(false), - IsNullExpr, - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForNoVarStatement() { - const string ForCode = @" -for(i = 0; i<4; i++) { } -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckForStmt( - CheckExprStmt( - CheckBinary( - JSToken.Assign, - CheckLookup("i"), - Zero - ) - ), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForInStatement() { - const string ForCode = @" -for(var i in []) { } -for(i in []) { } -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckForInStmt( - CheckVar(CheckVarDecl("i")), - CheckArrayLiteral(), - CheckBlock() - ), - CheckForInStmt( - CheckExprStmt(CheckLookup("i")), - CheckArrayLiteral(), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForContinueStatement() { - const string ForCode = @" -for(var i = 0; i<4; i++) { continue; } -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckContinue() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForBreakStatement() { - const string ForCode = @" -for(var i = 0; i<4; i++) { break; } -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckBreak() - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForContinueLabelStatement() { - const string ForCode = @" -for(var i = 0; i<4; i++) { continue myLabel; } -myLabel: -"; - CheckAst( - ParseCode( - ForCode, - new ErrorInfo(JSError.NoLabel, true, new IndexSpan(38, 7)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckContinue("myLabel") - ) - ), - CheckLabel("myLabel") - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForContinueLabelStatement2() { - const string ForCode = @" -for(var i = 0; i<4; i++) { -myLabel: -continue myLabel; -} -"; - CheckAst( - ParseCode( - ForCode, - new ErrorInfo(JSError.BadContinue, true, new IndexSpan(41, 16)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckLabel("myLabel", CheckContinue("myLabel")) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForContinueLabelStatement3() { - const string ForCode = @" -for(var j = 0; j<4; j++) { - myLabel: - for(var i = 0; i<4; i++) { - continue myLabel; - } -} -"; - CheckAst( - ParseCode( - ForCode - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("j", Zero)), - CheckLessThan(J, Four), - CheckIncrement(J), - CheckBlock( - CheckLabel( - "myLabel", - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckContinue("myLabel") - ) - ) - ) - ) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestForBreakLabelStatement() { - const string ForCode = @" -for(var i = 0; i<4; i++) { break myLabel; } -myLabel: -"; - CheckAst( - ParseCode( - ForCode, - new ErrorInfo(JSError.NoLabel, true, new IndexSpan(35, 7)) - ), - CheckBlock( - CheckForStmt( - CheckVar(CheckVarDecl("i", Zero)), - CheckLessThan(I, Four), - CheckIncrement(I), - CheckBlock( - CheckBreak("myLabel") - ) - ), - CheckLabel("myLabel") - ) - ); - } - - [TestMethod, Priority(0)] - public void TryStatement() { - const string ForCode = @" -try { -}catch(err) { -} -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckTryCatch( - CheckBlock(), - CheckParameterDeclaration("err"), - CheckBlock() - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void WhileStatement() { - const string ForCode = @" -while(i<0) { 2; } -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckWhileStmt( - CheckLessThan(I, Zero), - CheckBlock(CheckConstantStmt(Two)) - ) - ) - ); - } - - [TestMethod, Priority(0)] - public void TestUseStrictDirective() { - const string code = @"'use strict'; -42"; - - CheckAst( - ParseCode(code), - CheckBlock( - CheckExprStmt( - CheckDirectivePrologue("use strict", true) - ), - CheckConstantStmt(42.0) - ) - ); - } - - [TestMethod, Priority(0)] - public void DoWhileStatement() { - const string ForCode = @" -do { - 2 -}while(i<1); -"; - CheckAst( - ParseCode(ForCode), - CheckBlock( - CheckDoWhileStmt( - CheckBlock(CheckConstantStmt(Two)), - CheckLessThan(I, One) - ) - ) - ); - } - - #region Checker helpers - - private static Action Pass = CheckEmptyStmt(); - private static Action Zero = CheckConstant(0.0); - private static Action One = CheckConstant(1.0); - private static Action Two = CheckConstant(2.0); - private static Action Three = CheckConstant(3.0); - private static Action Four = CheckConstant(4.0); - private static Action Null = CheckConstant(null); - private static Action True = CheckConstant(true); - private static Action False = CheckConstant(false); - private static Action I = CheckLookup("i"); - private static Action J = CheckLookup("j"); - - private static Action IsNullExpr = expr => Assert.IsNull(expr); - private static Action IsNullStmt = stmt => Assert.IsNull(stmt); - - private Action CheckIfStmt(Action condition, Action trueBlock, Action falseBlock = null) { - return stmt => { - Assert.AreEqual(typeof(IfNode), stmt.GetType()); - - var ifNode = (IfNode)stmt; - condition(ifNode.Condition); - trueBlock(ifNode.TrueBlock); - if (falseBlock != null) { - falseBlock(ifNode.FalseBlock); - } else { - Assert.AreEqual(null, ifNode.FalseBlock); - } - }; - } - - private Action CheckTryCatch(Action tryBody, Action name, Action catchBody) { - return stmt => { - Assert.AreEqual(typeof(TryNode), stmt.GetType()); - - var tryNode = (TryNode)stmt; - tryBody(tryNode.TryBlock); - name(tryNode.CatchParameter); - catchBody(tryNode.CatchBlock); - }; - } - - private Action CheckTryFinally(Action tryBody, Action finallyBody) { - return stmt => { - Assert.AreEqual(typeof(TryNode), stmt.GetType()); - - var tryNode = (TryNode)stmt; - tryBody(tryNode.TryBlock); - Assert.IsNull(tryNode.CatchBlock); - Assert.IsNull(tryNode.CatchParameter); - finallyBody(tryNode.FinallyBlock); - }; - } - - private Action CheckParameterDeclaration(string name) { - return decl => { - Assert.AreEqual(typeof(ParameterDeclaration), decl.GetType()); - - var paramDecl = (ParameterDeclaration)decl; - Assert.AreEqual(name, paramDecl.Name); - }; - } - - private Action CheckWhileStmt(Action condition, Action body) { - return stmt => { - Assert.AreEqual(typeof(WhileNode), stmt.GetType()); - - var whileStmt = (WhileNode)stmt; - condition(whileStmt.Condition); - body(whileStmt.Body); - }; - } - - private Action CheckDoWhileStmt(Action body, Action condition) { - return stmt => { - Assert.AreEqual(typeof(DoWhile), stmt.GetType()); - var whileStmt = (DoWhile)stmt; - - body(whileStmt.Body); - condition(whileStmt.Condition); - }; - } - - private static Action CheckEmptyStmt() { - return expr => { - Assert.AreEqual(typeof(EmptyStatement), expr.GetType()); - }; - } - - private static Action CheckLessThan(Action operand1, Action operand2) { - return CheckBinary(JSToken.LessThan, operand1, operand2); - } - - private static Action CheckAssign(Action operand1, Action operand2) { - return CheckBinary(JSToken.Assign, operand1, operand2); - } - - private static Action CheckBinary(JSToken token, Action operand1, Action operand2) { - return expr => { - Assert.AreEqual(typeof(BinaryOperator), expr.GetType()); - var bin = (BinaryOperator)expr; - Assert.AreEqual(token, bin.OperatorToken); - operand1(bin.Operand1); - operand2(bin.Operand2); - }; - } - - private static Action CheckBinaryStmt(JSToken token, Action operand1, Action operand2) { - return expr => CheckExprStmt(CheckBinary(token, operand1, operand2)); - } - - private static Action CheckIncrement(Action operand, bool isPostFix = true) { - return expr => CheckUnary(JSToken.Increment, operand, isPostFix); - } - - private static Action CheckUnaryStmt(JSToken token, Action operand, bool isPostFix = false) { - return expr => CheckExprStmt(CheckUnary(token, operand, isPostFix)); - } - - private static Action CheckUnary(JSToken token, Action operand, bool isPostFix = false) { - return expr => { - Assert.AreEqual(typeof(UnaryOperator), expr.GetType()); - var bin = (UnaryOperator)expr; - operand(bin.Operand); - }; - } - - private static Action CheckLookup(string name) { - return expr => { - Assert.AreEqual(typeof(Lookup), expr.GetType()); - var nameExpr = (Lookup)expr; - Assert.AreEqual(nameExpr.Name, name); - }; - } - - private static Action CheckCall(Action function, params Action[] args) { - return expr => { - Assert.AreEqual(typeof(CallNode), expr.GetType()); - - - var callNode = (CallNode)expr; - function(callNode.Function); - - Assert.IsFalse(callNode.IsConstructor); - Assert.IsFalse(callNode.InBrackets); - - Assert.AreEqual(args.Length, callNode.Arguments.Length); - for (int i = 0; i < args.Length; i++) { - args[i](callNode.Arguments[i]); - } - }; - } - - private static Action CheckIndex(Action function, params Action[] args) { - return expr => { - Assert.AreEqual(typeof(CallNode), expr.GetType()); - - - var callNode = (CallNode)expr; - function(callNode.Function); - - Assert.IsFalse(callNode.IsConstructor); - Assert.IsTrue(callNode.InBrackets); - - Assert.AreEqual(args.Length, callNode.Arguments.Length); - for (int i = 0; i < args.Length; i++) { - args[i](callNode.Arguments[i]); - } - }; - } - - private static Action CheckMember(string name, Action root) { - return expr => { - Assert.AreEqual(typeof(Member), expr.GetType()); - - var member = (Member)expr; - Assert.AreEqual(name, member.Name); - root(member.Root); - }; - } - - - private static Action CheckLookupStmt(string name) { - return stmt => CheckExprStmt(CheckLookup(name)); - } - - private static Action CheckVar(params Action[] decls) { - return expr => { - Assert.AreEqual(typeof(Var), expr.GetType()); - var varNode = (Var)expr; - Assert.AreEqual(varNode.Count, decls.Length); - for (int i = 0; i < decls.Length; i++) { - decls[i](varNode[i]); - } - }; - } - - private static Action CheckVarDecl(string name, Action initializer = null) { - return decl => { - Assert.AreEqual(decl.Name, name); - if (initializer != null) { - initializer(decl.Initializer); - } else { - Assert.AreEqual(null, decl.Initializer); - } - }; - } - - private static Action CheckLexicalDecl(params Action[] decls) { - return expr => { - Assert.AreEqual(typeof(LexicalDeclaration), expr.GetType()); - var varNode = (LexicalDeclaration)expr; - Assert.AreEqual(varNode.Count, decls.Length); - for (int i = 0; i < decls.Length; i++) { - decls[i](varNode[i]); - } - }; - } - - - private static Action CheckConstant(object value) { - return expr => { - Assert.AreEqual(typeof(ConstantWrapper), expr.GetType()); - - Assert.AreEqual(value, ((ConstantWrapper)expr).Value); - }; - } - - private static Action CheckConstantStmt(object value) { - return stmt => CheckExprStmt(CheckConstant(value)); - } - - private static Action[] CheckConstantStmts(params object[] value) { - return value.Select(x => CheckConstantStmt(x)).ToArray(); - } - - class RegEx { - public readonly string Pattern; - public readonly string Switches; - - public RegEx(string pattern, string switches) { - Pattern = pattern; - Switches = switches; - } - - public static implicit operator RegEx(string pattern) { - return new RegEx(pattern, null); - } - } - - private static Action[] CheckRegExs(params RegEx[] values) { - return values.Select(x => CheckRegExStmt(x.Pattern, x.Switches)).ToArray(); - } - - private static Action CheckRegExStmt(string pattern, string switches = null) { - return stmt => CheckExprStmt(CheckRegEx(pattern, switches)); - } - - private static Action CheckRegEx(string pattern, string switches = null) { - return expr => { - Assert.AreEqual(typeof(RegExpLiteral), expr.GetType()); - - var regex = (RegExpLiteral)expr; - Assert.AreEqual(pattern, regex.Pattern); - Assert.AreEqual(switches, regex.PatternSwitches); - }; - } - - private static Action CheckYieldExpr(Action operand) { - return CheckYieldExpr(operand, false); - } - - private static Action CheckYieldExpr(Action operand, bool isYieldFrom) { - return expr => { - Assert.AreEqual(typeof(YieldExpression), expr.GetType()); - - var yield = (YieldExpression)expr; - if (operand != null) { - operand(yield.Operand); - } else { - Assert.IsNull(yield.Operand); - } - Assert.AreEqual(isYieldFrom, yield.YieldFrom); - }; - } - - private static Action CheckExprStmt(Action expr) { - return stmt => { - Assert.AreEqual(typeof(ExpressionStatement), stmt.GetType()); - - var exprStmt = (ExpressionStatement)stmt; - expr(exprStmt.Expression); - }; - } - - private static Action CheckForStmt(Action init, Action condition, Action increment, Action body) { - return stmt => { - Assert.AreEqual(typeof(ForNode), stmt.GetType()); - ForNode forStmt = (ForNode)stmt; - - init(forStmt.Initializer); - condition(forStmt.Condition); - increment(forStmt.Incrementer); - body(forStmt.Body); - }; - } - - private static Action CheckBlock(params Action[] statements) { - return stmt => { - Assert.AreEqual(typeof(Block), stmt.GetType()); - var block = (Block)stmt; - Assert.AreEqual(statements.Length, block.Count, "Statement Count not Equal to found in Block"); - for (int i = 0; i < block.Count; i++) { - try { - statements[i](block[i]); - } catch (AssertFailedException e) { - throw new AssertFailedException(String.Format("Block Item {0}: {1}", i, e.Message + Environment.NewLine + e.StackTrace.ToString()), e); - } - } - }; - } - - private static Action CheckDebuggerStmt() { - return stmt => { - Assert.AreEqual(typeof(DebuggerNode), stmt.GetType()); - }; - } - - private static Action CheckContinue(string label = null) { - return stmt => { - Assert.AreEqual(typeof(ContinueNode), stmt.GetType()); - - var contStmt = (ContinueNode)stmt; - if (label != null) { - Assert.AreEqual(label, contStmt.Label); - } else { - Assert.AreEqual(null, contStmt.Label); - } - }; - } - private static Action CheckBreak(string label = null) { - return stmt => { - Assert.AreEqual(typeof(Break), stmt.GetType()); - - var breakStmt = (Break)stmt; - if (label != null) { - Assert.AreEqual(label, breakStmt.Label); - } else { - Assert.AreEqual(null, breakStmt.Label); - } - }; - } - - private static Action CheckLabel(string label, Action statement = null) { - return stmt => { - Assert.AreEqual(typeof(LabeledStatement), stmt.GetType()); - - var labelStmt = (LabeledStatement)stmt; - Assert.AreEqual(label, labelStmt.Label); - if (statement != null) { - statement(labelStmt.Statement); - } else { - Assert.AreEqual(null, labelStmt.Statement); - } - }; - } - - private static Action CheckSwitch(Action expr, params Action[] cases) { - return stmt => { - Assert.AreEqual(typeof(Switch), stmt.GetType()); - - var switchStmt = (Switch)stmt; - expr(switchStmt.Expression); - Assert.AreEqual(cases.Length, switchStmt.Cases.Length); - for (int i = 0; i < cases.Length; i++) { - cases[i](switchStmt.Cases[i]); - } - }; - } - - private static Action CheckCase(Action expr, Action body) { - return stmt => { - Assert.AreEqual(typeof(SwitchCase), stmt.GetType()); - - var switchCase = (SwitchCase)stmt; - if (expr != null) { - expr(switchCase.CaseValue); - } else { - Assert.IsNull(switchCase.CaseValue, "expected default case"); - } - body(switchCase.Statements); - }; - } - - private Action CheckThrow(Action action = null) { - return stmt => { - Assert.AreEqual(typeof(ThrowNode), stmt.GetType()); - - var throwNode = (ThrowNode)stmt; - if (action != null) { - action(throwNode.Operand); - } else { - Assert.IsNull(throwNode.Operand, "expected no throw operand"); - } - }; - } - - private Action CheckWith(Action expr, Action body) { - return stmt => { - Assert.AreEqual(typeof(WithNode), stmt.GetType()); - - var withNode = (WithNode)stmt; - expr(withNode.WithObject); - body(withNode.Body); - }; - } - - private Action CheckConditional(Action condition, Action ifTrue, Action ifFalse) { - return expr => { - Assert.AreEqual(typeof(Conditional), expr.GetType()); - - var cond = (Conditional)expr; - condition(cond.Condition); - ifTrue(cond.TrueExpression); - ifFalse(cond.FalseExpression); - }; - } - - private Action CheckGrouping(Action operand) { - return expr => { - Assert.AreEqual(typeof(GroupingOperator), expr.GetType()); - - var grouping = (GroupingOperator)expr; - operand(grouping.Operand); - }; - } - - private Action CheckArrayLiteral(params Action[] values) { - return expr => { - Assert.AreEqual(typeof(ArrayLiteral), expr.GetType()); - - var array = (ArrayLiteral)expr; - Assert.AreEqual(values.Length, array.Elements.Length); - for (int i = 0; i < values.Length; i++) { - values[i](array.Elements[i]); - } - }; - } - - private Action CheckObjectLiteral(params Action[] values) { - return expr => { - Assert.AreEqual(typeof(ObjectLiteral), expr.GetType()); - - var array = (ObjectLiteral)expr; - Assert.AreEqual(values.Length, array.Properties.Length); - for (int i = 0; i < values.Length; i++) { - values[i](array.Properties[i]); - } - }; - } - - private Action Property(object name, Action value) { - return expr => { - Assert.AreEqual(typeof(ObjectLiteralProperty), expr.GetType()); - - var prop = (ObjectLiteralProperty)expr; - Assert.AreEqual(name, prop.Name.Value); - value(prop.Value); - }; - } - - private Action GetterSetter(bool isGetter, string name, Action value) { - return expr => { - Assert.AreEqual(typeof(ObjectLiteralProperty), expr.GetType()); - - var prop = (ObjectLiteralProperty)expr; - value(prop.Value); - - Assert.AreEqual(typeof(GetterSetter), prop.Name.GetType()); - var getterSetter = (GetterSetter)prop.Name; - Assert.AreEqual(name, prop.Name.Value); - Assert.AreEqual(isGetter, getterSetter.IsGetter); - }; - } - - private Action CheckFunctionObject(string name, Action body, params Action[] args) { - return CheckFunctionObject(name, body, false, args); - } - - private Action CheckFunctionObject(string name, Action body, bool isGenerator, params Action[] args) { - return stmt => { - Assert.AreEqual(typeof(FunctionObject), stmt.GetType()); - - var func = (FunctionObject)stmt; - Assert.AreEqual(name, func.Name); - Assert.AreEqual(isGenerator, func.IsGenerator); - - body(func.Body); - if (func.ParameterDeclarations != null) { - Assert.AreEqual(args.Length, func.ParameterDeclarations.Length); - for (int i = 0; i < func.ParameterDeclarations.Length; i++) { - args[i](func.ParameterDeclarations[i]); - } - } else { - Assert.IsNull(args); - } - }; - } - - private Action CheckFunctionExpr(Action functionObject) { - return expr => { - Assert.AreEqual(typeof(FunctionExpression), expr.GetType()); - - var funcExpr = (FunctionExpression)expr; - - functionObject(funcExpr.Function); - }; - } - - private Action CheckForInStmt(Action decl, Action collection, Action body) { - return stmt => { - Assert.AreEqual(typeof(ForIn), stmt.GetType()); - - var forIn = (ForIn)stmt; - decl(forIn.Variable); - collection(forIn.Collection); - }; - } - private Action CheckReturn(Action operand = null) { - return stmt => { - Assert.AreEqual(typeof(ReturnNode), stmt.GetType()); - - var ret = (ReturnNode)stmt; - if (ret.Operand == null) { - Assert.IsNull(operand); - } else { - Assert.IsNotNull(operand); - operand(ret.Operand); - } - }; - } - - - private Action CheckDirectivePrologue(string value, bool useStrict = false) { - return expr => { - Assert.AreEqual(typeof(DirectivePrologue), expr.GetType()); - - Assert.AreEqual(value, ((DirectivePrologue)expr).Value); - Assert.AreEqual(useStrict, ((DirectivePrologue)expr).UseStrict); - }; - } - - #endregion - - private static JsAst ParseCode(string code, params ErrorInfo[] errors) { - return ParseCode(code, false, errors); - } - - private static JsAst ParseCode(string code, bool collectWarnings, params ErrorInfo[] errors) { - CollectingErrorSink errorSink = new CollectingErrorSink(collectWarnings); - - var parser = new JSParser(code, errorSink); - var ast = parser.Parse(new CodeSettings()); - - if (errors != null) { - errorSink.CheckErrors(errors); - } - return ast; - } - - private static JsAst ParseExpression(string code, bool collectWarnings, params ErrorInfo[] errors) { - CollectingErrorSink errorSink = new CollectingErrorSink(collectWarnings); - - var parser = new JSParser(code, errorSink); - var ast = parser.Parse(new CodeSettings() { SourceMode = JavaScriptSourceMode.Expression }); - - errorSink.CheckErrors(errors); - return ast; - } - - private void CheckAst(JsAst ast, Action checkBody) { - checkBody(ast.Block); - - ast.Walk(new TestVisitor()); - - var newAst = SerializationTests.RoundTrip(ast); - - checkBody(newAst.Block); - newAst.Walk(new TestVisitor()); - } - - class TestVisitor : AstVisitor { - private void TestNode(Node node) { - if (node != null) { - Assert.IsNotNull(node.ToString()); - foreach (var child in node.Children) { - Assert.IsNotNull(child); - } - - IEnumerable enumerable = node as IEnumerable; - if (enumerable != null) { - foreach (var value in enumerable) { - Assert.IsNotNull(value); - } - } - } - } - - private void TestNodes(T[] nodes) where T : Node { - if (nodes != null) { - foreach (var node in nodes) { - Assert.IsNotNull(node); - TestNode(node); - } - } - } - - public override bool Walk(ArrayLiteral node) { TestNode(node); TestNodes(node.Elements); return base.Walk(node); } - public override bool Walk(BinaryOperator node) { TestNode(node); return base.Walk(node); } - public override bool Walk(CommaOperator node) { TestNode(node); return base.Walk(node); } - public override bool Walk(Block node) { TestNode(node); return base.Walk(node); } - public override bool Walk(Break node) { TestNode(node); return base.Walk(node); } - public override bool Walk(CallNode node) { TestNode(node); TestNodes(node.Arguments); return base.Walk(node); } - public override bool Walk(Conditional node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ConstantWrapper node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ConstStatement node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ContinueNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(DebuggerNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(DirectivePrologue node) { TestNode(node); return base.Walk(node); } - public override bool Walk(DoWhile node) { TestNode(node); return base.Walk(node); } - public override bool Walk(EmptyStatement node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ForIn node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ForNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(FunctionObject node) { TestNode(node); TestNodes(node.ParameterDeclarations); return base.Walk(node); } - public override bool Walk(GetterSetter node) { TestNode(node); return base.Walk(node); } - public override bool Walk(GroupingOperator node) { TestNode(node); return base.Walk(node); } - public override bool Walk(IfNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(LabeledStatement node) { TestNode(node); return base.Walk(node); } - public override bool Walk(LexicalDeclaration node) { TestNode(node); return base.Walk(node); } - public override bool Walk(Lookup node) { TestNode(node); return base.Walk(node); } - public override bool Walk(Member node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ObjectLiteral node) { TestNode(node); TestNodes(node.Properties); return base.Walk(node); } - public override bool Walk(ObjectLiteralField node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ObjectLiteralProperty node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ParameterDeclaration node) { TestNode(node); return base.Walk(node); } - public override bool Walk(RegExpLiteral node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ReturnNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(Switch node) { TestNode(node); TestNodes(node.Cases); return base.Walk(node); } - public override bool Walk(SwitchCase node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ThisLiteral node) { TestNode(node); return base.Walk(node); } - public override bool Walk(ThrowNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(TryNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(Var node) { TestNode(node); return base.Walk(node); } - public override bool Walk(VariableDeclaration node) { TestNode(node); return base.Walk(node); } - public override bool Walk(UnaryOperator node) { TestNode(node); return base.Walk(node); } - public override bool Walk(WhileNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(WithNode node) { TestNode(node); return base.Walk(node); } - public override bool Walk(JsAst jsAst) { TestNode(jsAst); return base.Walk(jsAst); } - public override bool Walk(FunctionExpression functionExpression) { TestNode(functionExpression); return base.Walk(functionExpression); } - public override bool Walk(ExpressionStatement node) { TestNode(node); return base.Walk(node); } - } - } - - class ErrorInfo { - public readonly bool IsError; - public readonly JSError Error; - public readonly IndexSpan Span; - - public ErrorInfo(JSError error, bool isError, IndexSpan span) { - Error = error; - IsError = isError; - Span = span; - } - - public override string ToString() { - return String.Format("{0}: JSError.{1} {2}", - IsError ? "Error" : "Warning", - Error, - Span - ); - } - } - - class CollectingErrorSink : ErrorSink { - private readonly List _errors = new List(); - public bool _collectWarnings; - - public CollectingErrorSink(bool collectWarnings = false) { - _collectWarnings = collectWarnings; - } - - public override void OnError(JScriptExceptionEventArgs error) { - error.Error.ToString(); - var result = new ErrorInfo(error.Error.ErrorCode, error.Error.IsError, error.Exception.Span); - - if (error.Error.IsError || _collectWarnings) { - _errors.Add(result); - } - } - - public void CheckErrors(ErrorInfo[] errors) { - bool success = false; - try { - Assert.AreEqual(errors.Length, Errors.Count, "Unexpected Error Count"); - for (int i = 0; i < errors.Length; i++) { - Assert.AreEqual(errors[i].ToString(), Errors[i].ToString()); - } - success = true; - } finally { - if (!success) { - foreach (var error in Errors) { - Console.WriteLine( - "new ErrorInfo(JSError.{0}, {1}, new IndexSpan({2}, {3})),", - error.Error, - error.IsError ? "true" : "false", - error.Span.Start, - error.Span.Length - ); - } - } - } - } - - public List Errors { - get { - return _errors; - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Microsoft.NodejsTools.Parsing; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace AnalysisTests { + [TestClass] + public class ParserTests { + [TestMethod, Priority(0)] + public void TestCodeSettings() { + var settings1 = new CodeSettings(); + var settings2 = new CodeSettings() { AllowShebangLine = true, SourceMode = JavaScriptSourceMode.Expression, StrictMode = true }; + var settings3 = new CodeSettings() { KnownGlobalNamesList = "foo;bar" }; + + foreach (var curSetting in new[] { settings1, settings2, settings3 }) { + var other = curSetting.Clone(); + Assert.AreEqual(curSetting.AllowShebangLine, other.AllowShebangLine); + Assert.AreEqual(curSetting.ConstStatementsMozilla, other.ConstStatementsMozilla); + Assert.AreEqual(curSetting.KnownGlobalNamesList, other.KnownGlobalNamesList); + Assert.AreEqual(curSetting.SourceMode, other.SourceMode); + Assert.AreEqual(curSetting.StrictMode, other.StrictMode); + } + } + + [TestMethod, Priority(0)] + public void TestSourceSpan() { + var span = new SourceSpan( + new SourceLocation(1, 1, 1), + new SourceLocation(100, 2, 100) + ); + + var span2 = span; + + Assert.IsTrue(span.Start < span.End); + AssertUtil.Throws(() => new SourceSpan(span.End, span.Start)); + AssertUtil.Throws(() => new SourceSpan(span.End, SourceLocation.Invalid)); + AssertUtil.Throws(() => new SourceSpan(SourceLocation.Invalid, span.Start)); + Assert.AreEqual(99, span.Length); + Assert.IsTrue(span == span2); + Assert.IsFalse(span != span2); + Assert.IsTrue(span.Equals(span2)); + } + + [Ignore] + [TestMethod, Priority(0)] + public void TestFunctionRecovery() { + const string code = @" +[1,2, +function Y() { + (abcde X()); +;abcde +} +] +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(33, 1)), + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(36, 1)), + new ErrorInfo(JSError.NoRightBracket, true, new IndexSpan(37, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(48, 1)) + ), + CheckBlock( + CheckExprStmt( + CheckArrayLiteral( + CheckConstant(1.0), + CheckConstant(2.0), + CheckFunctionExpr( + CheckFunctionObject( + "Y", + CheckBlock( + CheckExprStmt(CheckGrouping(CheckLookup("abcde"))), + CheckExprStmt(CheckCall(CheckLookup("X"))) + ) + ) + ) + ) + ), + CheckEmptyStmt(), + CheckLookupStmt("abcde") + ) + ); + } + + /// + /// https://nodejstools.codeplex.com/workitem/1194 + /// https://nodejstools.codeplex.com/workitem/1200 + /// + [TestMethod, Priority(0)] + public void TestParseUnterminatedFunctionInList() { + const string code = @"console.log(function(error, response) {"; + + CheckAst( + ParseCode( + code, + false, + new ErrorInfo(JSError.ErrorEndOfFile, true, new IndexSpan(39, 0)), + new ErrorInfo(JSError.UnclosedFunction, true, new IndexSpan(12, 25)), + new ErrorInfo(JSError.NoRightBracketOrComma, true, new IndexSpan(39, 0)) + ), + CheckBlock( + CheckExprStmt( + CheckCall( + CheckMember("log", CheckLookup("console")), + CheckFunctionExpr( + CheckFunctionObject( + null, + CheckBlock(), + CheckParameterDeclaration("error"), + CheckParameterDeclaration("response") + ) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestParseExpression() { + const string code = @" +42 +"; + CheckAst( + ParseExpression( + code, + false + ), + CheckBlock( + CheckExprStmt( + CheckConstant(42.0) + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void ErrorCallBadArgs() { + const string code = @" +foo(abc.'foo') +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(10, 5)) + ), + CheckBlock( + CheckExprStmt( + CheckCall( + CheckLookup("foo"), + CheckLookup("abc") + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorCallBadArgs2() { + const string code = @" +foo(abc.'foo' else function) +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(10, 5)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(29, 1)) + ), + CheckBlock( + CheckExprStmt( + CheckCall( + CheckLookup("foo"), + CheckLookup("abc") + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestMemberKeyword() { + const string code = @" +foo.get +"; + CheckAst( + ParseCode( + code + ), + CheckBlock( + CheckExprStmt( + CheckMember("get", CheckLookup("foo")) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorGroupingNoCloseParen() { + const string code = @" +(foo +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(8, 0)) + ), + CheckBlock( + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorGroupingNoSkipToken() { + const string code = @" +(else function) +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(3, 4)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(16, 1)) + ), + CheckBlock( + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorPostfixNoSkipToken() { + const string code = @" +(else++) +foo +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(3, 4)) + ), + CheckBlock( + ) + ); + } + + + [TestMethod, Priority(0)] + public void ErrorDoubleOverflow() { + const string code = @" +1e100000 +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NumericOverflow, true, new IndexSpan(2, 8)) + ), + CheckBlock( + CheckConstantStmt(new InvalidNumericErrorValue("1e100000")) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningDoubleBoundaries() { + const string code = @" +1.7976931348623157E+308; +-1.7976931348623157E+308 +"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NumericMaximum, false, new IndexSpan(2, 23)), + new ErrorInfo(JSError.NumericMaximum, false, new IndexSpan(29, 23)) + ), + CheckBlock( + CheckConstantStmt(1.79769e+308), + CheckConstantStmt(-1.79769e+308) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorWithStatementBody() { + const string code = @" +with(abc) { + else +} +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(19, 4)) + ), + CheckBlock( + CheckWith( + CheckLookup("abc"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorWithStatementBody2() { + const string code = @" +with(abc) { + throw else function +} +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(25, 4)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(40, 1)) + ), + CheckBlock( + CheckWith( + CheckLookup("abc"), + CheckBlock( + CheckThrow(IsNullExpr) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningWithStatementNoCurlys() { + const string code = @" +with(abc) + foo +"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.StatementBlockExpected, false, new IndexSpan(18, 0)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(7, 3)) + ), + CheckBlock( + CheckWith( + CheckLookup("abc"), + CheckBlock( + CheckLookupStmt("foo") + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorWithStatement() { + const string code = @" +with(abc.'foo') { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(11, 5)) + ), + CheckBlock( + CheckWith( + CheckLookup("abc"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorWithStatement2() { + const string code = @" +with(else) { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(7, 4)) + ), + CheckBlock( + CheckWith( + CheckConstant(true), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorWithStatement3() { + const string code = @" +with(else function) { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(7, 4)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(20, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(25, 1)) + ), + CheckBlock( + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorWithStatementNoParens() { + const string code = @" +with abc { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(7, 3)), + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(11, 1)) + ), + CheckBlock( + CheckWith( + CheckLookup("abc"), + CheckBlock() + ) + ) + ); + } + + [Ignore] + [TestMethod, Priority(0)] + public void ErrorArrayLiteralBad() { + const string code = @" +i = [foo foo]"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoRightBracket, true, new IndexSpan(11, 3)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckArrayLiteral( + CheckLookup("foo") + ) + ) + ), + CheckLookupStmt("foo") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorArrayLiteralBad2() { + const string code = @" +i = [foo.'abc']"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(11, 5)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckArrayLiteral( + CheckLookup("foo") + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorArrayLiteralBad3() { + const string code = @" +i = [foo.'abc' else function]"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(11, 5)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(30, 1)) + ), + CheckBlock( + CheckExprStmt( + CheckLookup("i") + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ArrayLiteralEmpty() { + const string code = @" +i = []"; + CheckAst( + ParseCode( + code + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckArrayLiteral( + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ArrayLiteralMissing() { + const string code = @" +i = [1,,]"; + CheckAst( + ParseCode( + code + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckArrayLiteral( + One, + CheckConstant(Missing.Value), + CheckConstant(Missing.Value) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorFunctionBadBody3() { + const string code = @" +function foo() { + throw else function +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(30, 4)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(45, 1)) + ), + CheckBlock( + CheckFunctionObject( + "foo", + CheckBlock( + CheckThrow(IsNullExpr) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorFunctionBadBody2() { + const string code = @" +function foo() { + else function +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(24, 4)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(39, 1)) + ), + CheckBlock( + CheckFunctionObject( + "foo", + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorFunctionBadBody() { + const string code = @" +function foo() { + else +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(24, 4)) + ), + CheckBlock( + CheckFunctionObject( + "foo", + CheckBlock() + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void ErrorFunctionBadArgList2() { + const string code = @" +function foo(arg if) { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoComma, true, new IndexSpan(19, 2)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(26, 1)) + ), + CheckBlock( + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorFunctionTypeDefinition() { + const string code = @" +function foo(arg arg) { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoCommaOrTypeDefinitionError, true, new IndexSpan(19, 3)) + ), + CheckBlock( + CheckFunctionObject( + "foo", + CheckBlock(), + CheckParameterDeclaration("arg"), + CheckParameterDeclaration("arg") + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorFunctionBadArgList() { + const string code = @" +function foo(] ) { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(15, 1)) + ), + CheckBlock( + CheckFunctionObject( + "foo", + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorFunctionExtraComma() { + const string code = @" +function foo(, ) { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(15, 1)) + ), + CheckBlock( + CheckFunctionObject( + "foo", + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorFunctionNoRightParen() { + const string code = @" +function foo( { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(16, 1)) + ), + CheckBlock( + CheckFunctionObject( + "foo", + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorFunctionNoRightParen2() { + const string code = @" +function foo(abc { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(19, 1)) + ), + CheckBlock( + CheckFunctionObject( + "foo", + CheckBlock(), + CheckParameterDeclaration("abc") + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestFunctionStrictMode() { + const string code = @" +function abc() { + 'use strict'; +}"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckFunctionObject( + "abc", + CheckBlock( + CheckExprStmt( + CheckDirectivePrologue("use strict", useStrict: true) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestFunctionKeywordIdentifier() { + const string code = @" +function get() { +}"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckFunctionObject( + "get", + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestGenerators() { + string code = @" +function *x() { + yield 1; +}"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckExprStmt( + CheckYieldExpr( + One + ) + ) + ), + true + ) + ) + ); + + code = @" +function *x() { + yield *1; +}"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckExprStmt( + CheckYieldExpr( + One, + true + ) + ) + ), + true + ) + ) + ); + + code = @" +function *x() { + yield 1 + 2; +}"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckExprStmt( + CheckYieldExpr( + CheckBinary( + JSToken.Plus, + One, + Two + ) + ) + ) + ), + true + ) + ) + ); + + } + + [TestMethod, Priority(0)] + public void TestYieldMember() { + string code = @" +function *x() { + abc.yield; +}"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckExprStmt( + CheckMember( + "yield", + CheckLookup("abc") + ) + ) + ), + true + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningYieldNoSemicolon() { + var code = @" +function *x() { + yield 4 4 +} +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(31, 1)) + ), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckExprStmt(CheckYieldExpr(Four)), + CheckExprStmt(Four) + ), + true + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorYieldVar() { + var code = @" +function *x() { + var yield; +} +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(27, 5)) + ), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckVar(CheckVarDecl("yield")) + ), + true + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorYieldLabel() { + var code = @" +function *x() { + while(true) { + continue yield; + } +} +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(55, 5)) + ), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckWhileStmt( + True, + CheckBlock( + CheckContinue(), + CheckExprStmt(CheckYieldExpr(null)) + ) + ) + ), + true + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningYieldSemiColonInsertion() { + var code = @" +function *x() { + yield 4 + 4 +} +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(30, 0)) + ), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckExprStmt(CheckYieldExpr(Four)), + CheckExprStmt(Four) + ), + true + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryYieldBadOperand() { + var code = @" +function *x() { + yield foo.'abc' +}"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(33, 5)) + ), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckExprStmt( + CheckYieldExpr( + CheckLookup("foo") + ) + ) + ), + true + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoverYieldBadOperand2() { + var code = @" +function *x() { + yield else +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(29, 4)) + ), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock( + CheckExprStmt(CheckYieldExpr(null)) + ), + true + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryYieldBadOperand3() { + var code = @" +function *x() { + yield else function +}"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(29, 4)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(44, 1)) + ), + CheckBlock( + CheckFunctionObject( + "x", + CheckBlock(CheckExprStmt(CheckYieldExpr(null))), + true + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorTryCatchNoTryOrFinally() { + const string code = @" +try { +} +foo"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoCatch, true, new IndexSpan(12, 3)) + ), + CheckBlock( + CheckTryFinally( + CheckBlock(), + CheckBlock() + ), + CheckLookupStmt("foo") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorTryCatchBadCatch() { + const string code = @" +try { +} catch(foo) { + else +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(29, 4)) + ), + CheckBlock( + CheckTryCatch( + CheckBlock(), + CheckParameterDeclaration("foo"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorTryCatchBadCatch2() { + const string code = @" +try { +} catch(foo) { + else function +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(29, 4)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(44, 1)) + ), + CheckBlock( + CheckBlock() + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorTryCatchNonIdentifierKeyword() { + const string code = @" +try { +} catch(function) { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(17, 8)), + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(25, 1)), + new ErrorInfo(JSError.ErrorEndOfFile, true, new IndexSpan(31, 0)), + new ErrorInfo(JSError.UnclosedBlock, true, new IndexSpan(17, 8)) + ), + CheckBlock( + CheckTryCatch( + CheckBlock(), + CheckParameterDeclaration("function"), + CheckBlock( + CheckFunctionObject(")", CheckBlock(), null) + ) + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void ErrorTryCatchIdentifierKeyword() { + const string code = @" +try { +} catch(get) { +}"; + CheckAst( + ParseCode( + code + ), + CheckBlock( + CheckTryCatch( + CheckBlock(), + CheckParameterDeclaration("get"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorTryCatchNoParens() { + const string code = @" +try { +} catch quox { +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(17, 4)), + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(22, 1)) + ), + CheckBlock( + CheckTryCatch( + CheckBlock(), + CheckParameterDeclaration("quox"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorTryBadBody() { + const string code = @" +try { + else +} catch(quox) { + foo +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(13, 4)) + ), + CheckBlock( + CheckTryCatch( + CheckBlock(), + CheckParameterDeclaration("quox"), + CheckBlock(CheckLookupStmt("foo")) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorTryBadBody2() { + const string code = @" +try { + else function +} catch(quox) { + foo +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(13, 4)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(28, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(54, 1)) + ), + CheckBlock( + CheckBlock(), + CheckLookupStmt("foo") + ) + ); + } + + + [TestMethod, Priority(0)] + public void ErrorTryMissingCurlys() { + const string code = @" +try + foo +catch(quox) + foo +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(11, 3)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(16, 5)), + new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(33, 3)) + ), + CheckBlock( + CheckTryCatch( + CheckBlock(CheckLookupStmt("foo")), + CheckParameterDeclaration("quox"), + CheckBlock(CheckLookupStmt("foo")) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningThrowSemicolonInsertion() { + const string code = @" +throw foo +foo"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(11, 0)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(8, 3)) + ), + CheckBlock( + CheckThrow( + CheckLookup("foo") + ), + CheckLookupStmt("foo") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorThrowNoSemicolon() { + const string code = @" +throw foo foo"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(12, 3)) + ), + CheckBlock( + CheckThrow( + CheckLookup("foo") + ), + CheckLookupStmt("foo") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorThrowBadExpression2() { + const string code = @" +throw foo.'abc' foo"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(12, 5)) + ), + CheckBlock( + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorThrowBadExpression() { + const string code = @" +throw foo.'abc' +foo"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(12, 5)) + ), + CheckBlock( + CheckThrow( + CheckLookup("foo") + ), + CheckLookupStmt("foo") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchCaseBadStatement() { + const string code = @" +switch(foo){ + case 0: + else +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(37, 4)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("foo"), + CheckCase( + Zero, + CheckBlock() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchCaseBadCase2() { + const string code = @" +switch(foo){ + case foo.'abc' else function +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(29, 5)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(50, 1)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("foo") + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchCaseBadCase() { + const string code = @" +switch(foo){ + case foo.'abc': +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(29, 5)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("foo"), + CheckCase( + CheckLookup("foo"), + CheckBlock() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchCaseMissingColon() { + const string code = @" +switch(foo){ + case 0 +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoColon, true, new IndexSpan(28, 1)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("foo"), + CheckCase( + Zero, + CheckBlock() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchNoCasesOrDefault() { + const string code = @" +switch(foo){ + foo +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.BadSwitch, true, new IndexSpan(20, 3)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("foo"), + CheckCase( + null, + CheckBlock( + CheckLookupStmt("foo") + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchDuplicateDefault() { + const string code = @" +switch(foo){ + default: + default: +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.DupDefault, true, new IndexSpan(34, 7)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("foo"), + CheckCase( + null, + CheckBlock() + ), + CheckCase( + null, + CheckBlock() + ) + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void ErrorSwitchBadExpressionNoLeftCurly() { + const string code = @" +switch(foo.'abc') + case 0: +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(13, 5)), + new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(26, 4)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("foo"), + CheckCase( + Zero, + CheckBlock() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchBadExpression() { + const string code = @" +switch(foo.'abc') { + case 0: +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(13, 5)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("foo"), + CheckCase( + Zero, + CheckBlock() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchBadExpression2() { + const string code = @" +switch(else) { + case 0: +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(9, 4)) + ), + CheckBlock( + CheckSwitch( + CheckConstant(true), + CheckCase( + Zero, + CheckBlock() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchBadExpression3() { + const string code = @" +switch(else function) { + case 0: +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(9, 4)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(22, 1)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(31, 4)) + ), + CheckBlock( + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchNoLeftCurly() { + const string code = @" +switch(abc) + case 0: +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(20, 4)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("abc"), + CheckCase( + Zero, + CheckBlock() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorSwitchNoParens() { + const string code = @" +switch abc { + case 0: +}"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(9, 3)), + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(13, 1)) + ), + CheckBlock( + CheckSwitch( + CheckLookup("abc"), + CheckCase( + Zero, + CheckBlock() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorBracketInvalidExpression() { + const string code = @" +i[foo.'abc'];"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(8, 5)) + ), + CheckBlock( + CheckExprStmt( + CheckIndex( + I, + CheckLookup("foo") + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorBracketInvalidExpression2() { + const string code = @" +i[foo.'abc' function];"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(8, 5)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(22, 1)) + ), + CheckBlock( + CheckExprStmt( + CheckIndex( + I, + CheckLookup("foo") + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorBracketInvalidExpression3() { + const string code = @" +i[else function];"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(4, 4)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(17, 1)) + ), + CheckBlock( + CheckExprStmt( + I + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorBracketInvalidExpression4() { + const string code = @" +i[function];"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoLeftCurly, true, new IndexSpan(13, 1)), + new ErrorInfo(JSError.ErrorEndOfFile, true, new IndexSpan(14, 0)), + new ErrorInfo(JSError.UnclosedFunction, true, new IndexSpan(4, 8)) + ), + CheckBlock( + CheckExprStmt( + CheckIndex( + I, + CheckFunctionExpr( + CheckFunctionObject( + null, + CheckBlock(), + null + ) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningOctalLiteral() { + const string code = @" +i = 0100;"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.OctalLiteralsDeprecated, false, new IndexSpan(6, 4)), + new ErrorInfo(JSError.OctalLiteralsDeprecated, false, new IndexSpan(6, 4)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(2, 1)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckConstant(64.0) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorObjectLiteralNoColon() { + const string code = @" +i = {abc:42, foo 0};"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoColon, true, new IndexSpan(19, 1)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckObjectLiteral( + Property("abc", CheckConstant(42.0)), + Property("foo", Zero) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorObjectLiteralNoRightCurly() { + const string code = @" +i = {abc:42, foo:0 +foo +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoRightCurly, true, new IndexSpan(22, 3)) + ), + CheckBlock( + CheckLookupStmt("i"), + CheckLookupStmt("foo") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorObjectLiteralNoComma() { + const string code = @" +i = {abc:42, foo:0 foo +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoComma, true, new IndexSpan(21, 3)), + new ErrorInfo(JSError.UnclosedObjectLiteral, true, new IndexSpan(6, 1)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckObjectLiteral( + Property("abc", CheckConstant(42.0)), + Property("foo", Zero) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorObjectLiteralBadExpression() { + const string code = @" +i = {abc:foo.'abc'};"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckObjectLiteral( + Property("abc", CheckLookup("foo")) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorObjectLiteralBadExpression2() { + const string code = @" +i = {abc:foo.'abc' else };"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckObjectLiteral( + Property("abc", CheckLookup("foo")) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorObjectLiteralBadExpression3() { + const string code = @" +i = {abc:foo.'abc' else, foo:42 };"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckObjectLiteral( + Property("abc", CheckLookup("foo")), + Property("foo", CheckConstant(42.0)) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorObjectLiteralNoMemberIdentifier() { + const string code = @" +i = {abc:foo, [:42};"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoMemberIdentifier, true, new IndexSpan(16, 1)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckObjectLiteral( + Property("abc", CheckLookup("foo")), + Property("[", CheckConstant(42.0)) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorObjectLiteralInvalidNumeric() { + const string code = @" +i = {abc:foo, 1e100000:42};"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NumericOverflow, true, new IndexSpan(16, 8)) + ), + CheckBlock( + CheckExprStmt( + CheckAssign( + I, + CheckObjectLiteral( + Property("abc", CheckLookup("foo")), + Property(new InvalidNumericErrorValue("1e100000"), CheckConstant(42.0)) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void VariableDeclarationBadInitializer2() { + const string code = @" +var i = foo.'abc' else function;"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(14, 5)) + ), + CheckBlock( + CheckVar(CheckVarDecl("i", CheckLookup("foo"))), + CheckEmptyStmt() + ) + ); + } + + /// + /// https://nodejstools.codeplex.com/workitem/1227 + /// + /// Verify that when we report the missing semicolon error that we properly + /// don't treat the current token twice. + /// + [TestMethod, Priority(0)] + public void InvalidTrailingStringLiteral() { + var preceedingTests = new[] { + new { Code = "var i = 0", Expected = CheckVar(CheckVarDecl("i", Zero)) }, + new { Code = "break", Expected = CheckBreak() }, + new { Code = "continue", Expected = CheckContinue() }, + new { Code = "return 0", Expected = CheckReturn(Zero) }, + new { Code = "throw 0", Expected = CheckThrow(Zero) }, + }; + foreach (var test in preceedingTests) { + Console.WriteLine(test.Code); + string code = test.Code + "'"; + CheckAst( + ParseCode( + code, + null + ), + CheckBlock( + test.Expected, + CheckConstantStmt("") + ) + ); + } + } + + [TestMethod, Priority(0)] + public void VariableDeclarationBadInitializer3() { + const string code = @" +var j = 0, i = foo.'abc' else function;"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(21, 5)) + ), + CheckBlock( + CheckVar( + CheckVarDecl("j", Zero), + CheckVarDecl("i", CheckLookup("foo")) + ), + CheckEmptyStmt() + ) + ); + } + + [TestMethod, Priority(0)] + public void VariableDeclarationBadInitializer() { + const string code = @" +var i = foo.'abc';"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(14, 5)) + ), + CheckBlock( + CheckVar( + CheckVarDecl("i", CheckLookup("foo")) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void VariableDeclarationEqual() { + const string code = @" +var i == 1;"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoEqual, true, new IndexSpan(8, 2)) + ), + CheckBlock( + CheckVar( + CheckVarDecl("i", One) + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void ErrorDebuggerMissingSemiColon() { + const string code = @" +debugger foo"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(11, 3)) + ), + CheckBlock( + CheckDebuggerStmt(), + CheckLookupStmt("foo") + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningDebuggerMissingSemiColon() { + const string code = @" +debugger +foo"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(10, 0)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(12, 3)) + ), + CheckBlock( + CheckDebuggerStmt(), + CheckLookupStmt("foo") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorDuplicateLabel() { + const string code = @" +label: +label: +blah; +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.BadLabel, true, new IndexSpan(10, 5)) + ), + CheckBlock( + CheckLabel( + "label", + CheckBlock() + ), + CheckLookupStmt("blah") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorBadContinueBadLabel() { + const string code = @" +foo: +continue foo +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.BadContinue, true, new IndexSpan(8, 12)) + ), + CheckBlock( + CheckLabel( + "foo", + CheckContinue("foo") + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningContinueSemicolonInsertion() { + const string code = @" +for(var i = 0; i<4; i++) { + continue + blah +} +"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(42, 0)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(48, 4)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckContinue(), + CheckLookupStmt("blah") + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningBreakSemicolonInsertion() { + const string code = @" +for(var i = 0; i<4; i++) { + break + blah +} +"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(39, 0)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(45, 4)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckBreak(), + CheckLookupStmt("blah") + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorContinueNoSemicolon() { + const string code = @" +for(var i = 0; i<4; i++) { + continue if(true) { } +} +"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(43, 2)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckContinue(), + CheckIfStmt(True, CheckBlock()) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorBreakNoSemicolon() { + const string code = @" +for(var i = 0; i<4; i++) { + break if(true) { } +} +"; + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(40, 2)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckBreak(), + CheckIfStmt(True, CheckBlock()) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorBadContinue() { + const string code = @" +continue +blah +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.BadContinue, true, new IndexSpan(2, 8)) + ), + CheckBlock( + CheckContinue(), + CheckLookupStmt("blah") + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorBadBreak() { + const string code = @" +break +blah +"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.BadBreak, true, new IndexSpan(2, 5)) + ), + CheckBlock( + CheckBreak(), + CheckLookupStmt("blah") + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningWithNoParens() { + var code = @" +with abc { +} +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(7, 3)), + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(11, 1)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(7, 3)) + ), + CheckBlock( + CheckWith( + CheckLookup("abc"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningReturnNoSemicolon() { + var code = @" +return 4 4 +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(11, 1)) + ), + CheckBlock( + CheckReturn(Four), + CheckExprStmt(Four) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningReturnSemiColonInsertion() { + var code = @" +return 4 +4 +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SemicolonInsertion, false, new IndexSpan(10, 0)) + ), + CheckBlock( + CheckReturn(Four), + CheckExprStmt(Four) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryReturnBadOperand() { + var code = @" +return foo.'abc' + +{}"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(13, 5)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(23, 1)) + ), + CheckBlock( + CheckReturn( + CheckLookup("foo") + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoverReturnBadOperand2() { + var code = @" +return else + +{} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(9, 4)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(18, 1)) + ), + CheckBlock( + CheckReturn() + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryReturnBadOperand3() { + var code = @" +return else function"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(9, 4)) + ), + CheckBlock( + CheckReturn() + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorIfMissingParens() { + var code = @" +if true { +}"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(5, 4)), + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(10, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningIfSuspectAssign() { + var code = @" +if(i = 1) { +}"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SuspectAssignment, false, new IndexSpan(5, 5)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(5, 1)) + ), + CheckBlock( + CheckIfStmt( + CheckAssign(I, One), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningIfSuspectSemicolon() { + var code = @" +if(true);"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SuspectSemicolon, false, new IndexSpan(10, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + CheckEmptyStmt() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningIfElseSuspectSemicolon() { + var code = @" +if(true) { } +else;"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SuspectSemicolon, false, new IndexSpan(20, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + ), + CheckBlock( + CheckEmptyStmt() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningIfElseStatementBlock() { + var code = @" +if(true) { } +else i"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.StatementBlockExpected, false, new IndexSpan(21, 0)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(21, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + ), + CheckBlock( + CheckExprStmt(I) + ) + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void RecoveryIfBadConditional() { + var code = @" +if(foo.'abc') { +}"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(9, 5)) + ), + CheckBlock( + CheckIfStmt( + CheckLookup("foo"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryIfBadConditional2() { + var code = @" +if(else) { +}"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(5, 4)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryIfBadConditional3() { + var code = @" +if(else function) { +}"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(5, 4)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(18, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(23, 1)) + ), + CheckBlock( + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryIfBadBody() { + var code = @" +if(true) + foo.'abc' + +{ }"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(21, 5)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(32, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + CheckExprStmt(CheckLookup("foo")) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryIfBadBody2() { + var code = @" +if(true) + } + +{ }"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(17, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(24, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryIfBadBody3() { + var code = @" +if(true) + } function + +{ }"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(17, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + ) + ), + CheckBlock() + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryIfElseBadBody() { + var code = @" +if(true) { +} else + foo.'abc' + +{ }"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(30, 5)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(41, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + ), + CheckBlock( + CheckExprStmt(CheckLookup("foo")) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryIfElseBadBody2() { + var code = @" +if(true) { +} else + } + +{ }"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(27, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(34, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + ), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryIfElseBadBody3() { + var code = @" +if(true) { +} else + } function + +{ }"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(26, 1)) + ), + CheckBlock( + CheckIfStmt( + True, + CheckBlock( + ), + CheckBlock() + ), + CheckBlock() + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningWhileSuspectAssignment() { + var code = @" +while(i = 4) { +} +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SuspectAssignment, false, new IndexSpan(8, 5)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(8, 1)) + ), + CheckBlock( + CheckWhileStmt( + CheckAssign(I, Four), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryWhileBadBody() { + var code = @" +while(true) + foo.'abc' + +{ +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(24, 5)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(36, 1)) + ), + CheckBlock( + CheckWhileStmt( + True, + CheckBlock( + CheckExprStmt(CheckLookup("foo")) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryWhileBadBody2() { + var code = @" +while(true) + else + +{ } +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(20, 4)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(30, 1)) + ), + CheckBlock( + CheckWhileStmt( + True, + CheckBlock( + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryWhileBadCondition() { + var code = @" +while(foo.'abc') { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(12, 5)) + ), + CheckBlock( + CheckWhileStmt( + CheckLookup("foo"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryWhileBadCondition2() { + var code = @" +while(else) { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(8, 4)) + ), + CheckBlock( + CheckWhileStmt( + False, + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryWhileBadCondition3() { + var code = @" +while(else function) { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(8, 4)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(21, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(26, 1)) + ), + CheckBlock( + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorWhileNoOpenParens() { + var code = @" +while true { +} +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(8, 4)), + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(13, 1)) + ), + CheckBlock( + CheckWhileStmt( + True, + CheckBlock() + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void RecoveryDoWhileBadCondition() { + var code = @" +do { + true +} while(foo.'abc'); +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(30, 5)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock( + CheckExprStmt(True) + ), + CheckLookup("foo") + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryDoWhileBadCondition2() { + var code = @" +do { + true +} while(< function() { }); +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(26, 1)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(37, 1)), + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(39, 1)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(42, 1)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock( + CheckExprStmt(True) + ), + CheckConstant(false) + ), + CheckExprStmt(CheckGrouping(IsNullExpr)), + CheckBlock() + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorDoWhileMissngParens() { + var code = @" +do { + true +} while true; +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(26, 4)), + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(30, 1)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock( + CheckExprStmt(True) + ), + True + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void ErrorDoWhileNoWhile() { + var code = @" +do { + true +} (true); +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.NoWhile, true, new IndexSpan(20, 1)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock( + CheckExprStmt(True) + ), + True + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void WarningDoWhileMissingOpenCurly() { + var code = @" +do + true +while(true); +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.StatementBlockExpected, false, new IndexSpan(11, 0)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock( + CheckExprStmt(True) + ), + True + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningDoWhileSuspectAssignment() { + var code = @" +do { + true +} while(i = 4); +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SuspectAssignment, false, new IndexSpan(26, 5)), + new ErrorInfo(JSError.UndeclaredVariable, false, new IndexSpan(26, 1)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock( + CheckExprStmt(True) + ), + CheckAssign( + I, + Four + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryDoWhileBadBody() { + var code = @" +do + foo.'abc' +while(true); +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock( + CheckExprStmt(CheckLookup("foo")) + ), + True + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryDoWhileBadBody2() { + var code = @" +do + else +while(true); +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(10, 4)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock(), + True + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryDoWhileBadBody3() { + var code = @" +do + else function +while(true); +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.InvalidElse, true, new IndexSpan(10, 4)) + ), + CheckBlock( + CheckDoWhileStmt( + CheckBlock(), + False + ), + CheckWhileStmt( + True, + CheckBlock( + CheckEmptyStmt() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningForAssignment() { + var code = @" +for(var i = 0; i = 4; i++) { +} +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.SuspectAssignment, false, new IndexSpan(17, 5)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckAssign(I, Four), + CheckIncrement(I), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WarningForStatementBlock() { + var code = @" +for(var i = 0; i < 4; i++) + i++; +"; + + CheckAst( + ParseCode( + code, + true, + new ErrorInfo(JSError.StatementBlockExpected, false, new IndexSpan(35, 0)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckExprStmt( + CheckIncrement(I) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForIn() { + var code = @" +for(x in ) { +} + +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(11, 1)) + ), + CheckBlock( + CheckForInStmt( + CheckExprStmt(CheckLookup("x")), + CheckConstant(true), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForInBadCollection() { + var code = @" +for(x in foo.'abc') { +} + +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(15, 5)) + ), + CheckBlock( + CheckForInStmt( + CheckExprStmt(CheckLookup("x")), + CheckLookup("foo"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForIn2() { + var code = @" +for(x in < function() { }) { +} + +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(11, 1)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(22, 1)), + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(24, 1)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(27, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(32, 1)) + ), + CheckBlock( + CheckExprStmt(CheckGrouping(IsNullExpr)), + CheckBlock() + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForInBadBody() { + // foo.'abc' will trigger a recovery w/ an expression which can be + // promoted to an expression statement + var code = @" +for(x in abc) + foo.'abc' + +{ +} + +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(26, 5)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(38, 1)) + ), + CheckBlock( + CheckForInStmt( + CheckExprStmt(CheckLookup("x")), + CheckLookup("abc"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForInBadBody2() { + // } will trigger a recovery w/ an expression + var code = @" +for(x in abc) + } + +{ +} + +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(22, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(30, 1)) + ), + CheckBlock( + CheckForInStmt( + CheckExprStmt(CheckLookup("x")), + CheckLookup("abc"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForNoClosingParen() { + // } will trigger a recovery w/ an expression + var code = @" +for(x in abc { +} + +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(15, 1)) + ), + CheckBlock( + CheckForInStmt( + CheckExprStmt(CheckLookup("x")), + CheckLookup("abc"), + CheckBlock() + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void RecoveryForBadBody() { + // foo.'abc' will trigger a recovery w/ an expression which can be + // promoted to an expression statement + var code = @" +for(var i = 0; i< 4; i++) + foo.'abc' + +{ +} + +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(38, 5)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(50, 1)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckExprStmt( + CheckLookup("foo") + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForBadBody2() { + // } will trigger a recovery w/ an expression + var code = @" +for(var i = 0; i< 4; i++) + } + +{ +} + +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(34, 1)), + new ErrorInfo(JSError.SyntaxError, true, new IndexSpan(42, 1)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + ) + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void RecoveryForNoOpenParen() { + var code = @" +for var i = 0; i<4; i++) { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoLeftParenthesis, true, new IndexSpan(6, 3)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForMissingClosingParen() { + var code = @" +for(var i = 0; i<4; i++ { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoRightParenthesis, true, new IndexSpan(26, 1)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForMissingIncrement() { + var code = @" +for(var i = 0; i<4) { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(20, 1)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + IsNullExpr, + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForMissingConditionColon() { + var code = @" +for(var i = 0 : ) { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(16, 1)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckConstant(true), + IsNullExpr, + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForMissingConditionColonSemicolon() { + var code = @" +for(var i = 0 : ; i < 4) { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(16, 1)), + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(25, 1)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + IsNullExpr, + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForUnknownFollowingToken() { + var code = @" +for(var i = 0, j = 0; < function() { } { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(24, 1)), + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(35, 1)), + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(37, 1)) + ), + CheckBlock( + CheckExprStmt(CheckGrouping(IsNullExpr)), + CheckBlock(), + CheckBlock() + ) + ); + } + + [TestMethod, Priority(0)] + public void RecoveryForMissingConditionMultipleVars() { + var code = @" +for(var i = 0, j = 0) { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoSemicolon, true, new IndexSpan(22, 1)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero), CheckVarDecl("j", Zero)), + CheckConstant(true), + IsNullExpr, + CheckBlock() + ) + ) + ); + } + + /// + /// let not in strict mode should be fine... + /// + [TestMethod, Priority(0)] + public void RecoveryForMissingCondition() { + var code = @" +for(var i = 0;) { +} +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(16, 1)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckConstant(true), + IsNullExpr, + CheckBlock() + ) + ) + ); + + + + code = @" +for(var i = 0; +"; + + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.ExpressionExpected, true, new IndexSpan(18, 0)) + ), + CheckBlock( + ) + ); + } + + /// + /// let not in strict mode should be fine... + /// + [TestMethod, Priority(0)] + public void ForMultipleVariables() { + var code = @" +for(var i = 0, j = 1; i<4; i++) { +} +"; + + CheckAst( + ParseCode(code), + CheckBlock( + CheckForStmt( + CheckVar( + CheckVarDecl("i", Zero), + CheckVarDecl("j", One) + ), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock() + ) + ) + ); + } + + /// + /// let not in strict mode should be fine... + /// + [TestMethod, Priority(0)] + public void LetNotInStrictMode() { + var code = @" + let = _(let).succ(); +"; + + CheckAst( + ParseCode(code), + CheckBlock( + CheckBinaryStmt( + JSToken.Assign, + CheckLookup("let"), + CheckCall( + CheckMember( + "succ", + CheckCall( + CheckLookup("_"), + CheckLookup("let") + ) + ) + ) + ) + ) + ); + } + + /// + + /// IdentifierName :: + /// IdentifierStart + /// IdentifierName IdentifierPart + /// IdentifierStart :: + /// UnicodeLetter + /// $ + /// _ + /// \ UnicodeEscapeSequence + ///IdentifierPart :: + /// IdentifierStart + /// UnicodeCombiningMark + /// UnicodeDigit + /// UnicodeConnectorPunctuation + /// \u200C (ZWNJ) + /// \u200D (ZWJ) + /// + [TestMethod, Priority(0)] + public void IdentifierNames() { + const string identifiers = @" +$ +$f +_ +_f +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckLookupStmt("$"), + CheckLookupStmt("$f"), + CheckLookupStmt("_"), + CheckLookupStmt("_f") + ) + ); + } + + [TestMethod, Priority(0)] + public void TestUnicodeIdentifier() { + const string identifiers = @" +\u1234abc +\u1234\u1235abc +abc\u1234 +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckLookupStmt("\u1234abc"), + CheckLookupStmt("\u1234\u1235abc"), + CheckLookupStmt("abc\u1234") + ) + ); + } + + [TestMethod, Priority(0)] + public void TestBlock() { + const string code = @" +{ 1; 2 }"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckBlock( + CheckConstantStmt(One), + CheckConstantStmt(Two) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestDebugger() { + const string code = @" +debugger;"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckDebuggerStmt() + ) + ); + } + + [TestMethod, Priority(0)] + public void TestSwitch() { + const string code = @" +switch(abc) { + case 0: + case ""abc"": + 1; + break; + default: + 2; + break; +}"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckSwitch( + CheckLookup("abc"), + CheckCase( + Zero, + CheckBlock() + ), + CheckCase( + CheckConstant("abc"), + CheckBlock( + CheckConstantStmt(1.0), + CheckBreak() + ) + ), + CheckCase( + null, + CheckBlock( + CheckConstantStmt(2.0), + CheckBreak() + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestThrow() { + const string code = @" +throw ""abc""; +throw; +"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckThrow(CheckConstant("abc")), + CheckThrow() + ) + ); + } + + [TestMethod, Priority(0)] + public void TestWith() { + const string code = @" +with(abc) { + 1; +} +"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckWith( + CheckLookup("abc"), + CheckBlock( + CheckConstantStmt(1.0) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestConditional() { + const string identifiers = @" +1 ? 2 : 3 +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckExprStmt( + CheckConditional( + One, + Two, + Three + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestVoid() { + const string identifiers = @" +void 1 +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckExprStmt( + CheckUnary( + JSToken.Void, + One + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestTypeOf() { + const string identifiers = @" +typeof 1 +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckExprStmt( + CheckUnary( + JSToken.TypeOf, + One + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestUnary() { + const string identifiers = @" ++1; +-1; +~1; +!1; +delete abc; +++abc; +--abc; +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckUnaryStmt(JSToken.Plus, One), + CheckUnaryStmt(JSToken.Minus, One), + CheckUnaryStmt(JSToken.BitwiseNot, One), + CheckUnaryStmt(JSToken.LogicalNot, One), + CheckUnaryStmt(JSToken.Delete, CheckLookup("abc")), + CheckUnaryStmt(JSToken.Increment, CheckLookup("abc")), + CheckUnaryStmt(JSToken.Decrement, CheckLookup("abc")) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestUnaryPostFix() { + const string identifiers = @" +abc++; +abc--; +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckUnaryStmt(JSToken.Increment, CheckLookup("abc"), isPostFix: true), + CheckUnaryStmt(JSToken.Decrement, CheckLookup("abc"), isPostFix: true) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestGrouping() { + const string identifiers = @" +(1) +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckExprStmt(CheckGrouping(One)) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestArrayLiteral() { + const string identifiers = @" +[1,2,3]; +[1,2,3,] +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckExprStmt(CheckArrayLiteral(One, Two, Three)), + CheckExprStmt(CheckArrayLiteral(One, Two, Three)) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestObjectLiteral() { + // Expression statements can't start with { as it's ambiguous with block, + // so we do an assignment here + const string identifiers = @" +x = {'abc':1} +x = {'abc':1,} +x = {abc:1} +x = {42:1,} +x = {get abc () { 42 }} +x = {set abc (value) { 42 }} +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckBinaryStmt( + JSToken.Assign, + CheckLookup("x"), + CheckObjectLiteral(Property("abc", One)) + ), + CheckBinaryStmt( + JSToken.Assign, + CheckLookup("x"), + CheckObjectLiteral(Property("abc", One)) + ), + CheckBinaryStmt( + JSToken.Assign, + CheckLookup("x"), + CheckObjectLiteral(Property("abc", One)) + ), + CheckBinaryStmt( + JSToken.Assign, + CheckLookup("x"), + CheckObjectLiteral(Property(42.0, One)) + ), + CheckBinaryStmt( + JSToken.Assign, + CheckLookup("abc"), + CheckObjectLiteral( + GetterSetter( + true, + "abc", + CheckFunctionExpr( + CheckFunctionObject( + null, + CheckBlock() + ) + ) + ) + ) + ), + CheckBinaryStmt( + JSToken.Assign, + CheckLookup("abc"), + CheckObjectLiteral( + GetterSetter( + false, + "abc", + CheckFunctionExpr( + CheckFunctionObject( + null, + CheckBlock(), + CheckParameterDeclaration("value") + ) + ) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestPrecedence() { + const string code = @" +1 + 2 * 3; +1 + 2 / 3; +1 + 2 % 3; +1 - 2 * 3; +1 - 2 / 3; +1 - 2 % 3; +1 << 2 + 3; +1 << 2 - 3; +1 >> 2 + 3; +1 >> 2 - 3; +1 >>> 2 + 3; +1 >>> 2 - 3; +1 < 2 >> 3; +1 > 2 >> 3; +1 <= 2 >> 3; +1 >= 2 >> 3; +1 instanceof 2 >> 3; +1 in 2 >> 3; +1 == 2 < 3; +1 != 2 < 3; +1 === 2 < 3; +1 !== 2 < 3; +1 & 2 == 3; +1 ^ 2 & 3; +1 | 2 ^ 3; +1 && 2 | 3 +1 || 2 && 3 +1 ? 2 : 3 || 4 +"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckPrecedence(JSToken.Plus, JSToken.Multiply), + CheckPrecedence(JSToken.Plus, JSToken.Divide), + CheckPrecedence(JSToken.Plus, JSToken.Modulo), + CheckPrecedence(JSToken.Minus, JSToken.Multiply), + CheckPrecedence(JSToken.Minus, JSToken.Divide), + CheckPrecedence(JSToken.Minus, JSToken.Modulo), + CheckPrecedence(JSToken.LeftShift, JSToken.Plus), + CheckPrecedence(JSToken.LeftShift, JSToken.Minus), + CheckPrecedence(JSToken.RightShift, JSToken.Plus), + CheckPrecedence(JSToken.RightShift, JSToken.Minus), + CheckPrecedence(JSToken.UnsignedRightShift, JSToken.Plus), + CheckPrecedence(JSToken.UnsignedRightShift, JSToken.Minus), + CheckPrecedence(JSToken.LessThan, JSToken.RightShift), + CheckPrecedence(JSToken.GreaterThan, JSToken.RightShift), + CheckPrecedence(JSToken.LessThanEqual, JSToken.RightShift), + CheckPrecedence(JSToken.GreaterThanEqual, JSToken.RightShift), + CheckPrecedence(JSToken.InstanceOf, JSToken.RightShift), + CheckPrecedence(JSToken.In, JSToken.RightShift), + CheckPrecedence(JSToken.Equal, JSToken.LessThan), + CheckPrecedence(JSToken.NotEqual, JSToken.LessThan), + CheckPrecedence(JSToken.StrictEqual, JSToken.LessThan), + CheckPrecedence(JSToken.StrictNotEqual, JSToken.LessThan), + CheckPrecedence(JSToken.BitwiseAnd, JSToken.Equal), + CheckPrecedence(JSToken.BitwiseXor, JSToken.BitwiseAnd), + CheckPrecedence(JSToken.BitwiseOr, JSToken.BitwiseXor), + CheckPrecedence(JSToken.LogicalAnd, JSToken.BitwiseOr), + CheckPrecedence(JSToken.LogicalOr, JSToken.BitwiseAnd), + CheckExprStmt( + CheckConditional( + One, + Two, + CheckBinary(JSToken.LogicalOr, Three, Four) + ) + ) + ) + ); + } + + private static Action CheckPrecedence(JSToken lower, JSToken higher) { + return CheckBinaryStmt( + lower, + One, + CheckBinary( + higher, + Two, + Three + ) + ); + } + + /// + /// NullLiteral + /// null + /// BooleanLiteral + /// true false + /// + [TestMethod, Priority(0)] + public void SimpleLiterals() { + const string identifiers = @" +null +true +false +"; + CheckAst( + ParseCode(identifiers), + CheckBlock( + CheckConstantStmt(null), + CheckConstantStmt(true), + CheckConstantStmt(false) + ) + ); + } + + /// + /// NumericLiteral + /// DecimalLiteral + /// HexIntegerLiteral + /// + /// DecimalLiteral :: + /// DecimalIntegerLiteral . DecimalDigitsopt ExponentPartopt + /// . DecimalDigits ExponentPartopt + /// DecimalIntegerLiteral ExponentPartopt + /// DecimalIntegerLiteral :: + /// 0 + /// NonZeroDigit DecimalDigitsopt + /// DecimalDigits :: + /// DecimalDigit + /// DecimalDigits DecimalDigit + /// DecimalDigit :: one of + /// 0 1 2 3 4 5 6 7 8 9 + /// NonZeroDigit :: one of + /// 1 2 3 4 5 6 7 8 9 + /// ExponentPart :: + /// ExponentIndicator SignedInteger + /// ExponentIndicator :: one of + /// e E + /// SignedInteger :: + /// DecimalDigits + /// + DecimalDigits + /// - DecimalDigits + /// HexIntegerLiteral :: + /// 0x HexDigit + /// 0X HexDigit + /// HexIntegerLiteral HexDigit + /// HexDigit :: one of + /// 0 1 2 3 4 5 6 7 8 9 a b c d e f A B C D E F + /// + [Ignore] + [TestMethod, Priority(0)] + public void TestNumericLiterals() { + const string numbers = @" +0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +0.0 +1.1 +10 +0e1 +0e+1 +0e-1 +0x0 +0X0 +0x1 +0x2 +0x3 +0x4 +0x5 +0x6 +0x7 +0x8 +0x9 +0xA +0xB +0xC +0xD +0xE +0xF +0x10 +"; + CheckAst( + ParseCode(numbers), + CheckBlock( + CheckConstantStmts( + 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 0.0, + 1.1, 10.0, 0e1, 0e+1, 0e-1, 0.0, 0.0, 1.0, 2.0, 3.0, + 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, + 13.0, 14.0, 15.0, 16.0 + ) + ) + ); + } + + /// + /// StringLiteral :: + /// " DoubleStringCharactersopt " + /// ' SingleStringCharactersopt ' + /// DoubleStringCharacters :: + /// DoubleStringCharacter DoubleStringCharactersopt + /// SingleStringCharacters :: + /// SingleStringCharacter SingleStringCharactersopt + /// DoubleStringCharacter :: + /// SourceCharacter but not one of " or \ or LineTerminator + /// \ EscapeSequence + /// LineContinuation + /// SingleStringCharacter :: + /// SourceCharacter but not one of ' or \ or LineTerminator + /// \ EscapeSequence + /// LineContinuation + /// LineContinuation :: + /// \ LineTerminatorSequence + /// EscapeSequence :: + /// CharacterEscapeSequence + /// 0 [lookahead ∉ DecimalDigit] + /// HexEscapeSequence + /// UnicodeEscapeSequence + /// CharacterEscapeSequence :: + /// SingleEscapeCharacter + /// NonEscapeCharacter + /// SingleEscapeCharacter :: one of + /// ' " \ b f n r t v + /// NonEscapeCharacter :: + /// SourceCharacter but not one of EscapeCharacter or LineTerminator + /// EscapeCharacter :: + /// SingleEscapeCharacter + /// DecimalDigit + /// x + /// u + /// HexEscapeSequence :: + /// x HexDigit HexDigit + /// UnicodeEscapeSequence :: + /// u HexDigit HexDigit HexDigit HexDigit + /// + [TestMethod, Priority(0)] + public void TestStringLiterals() { + const string strings = @" +""hello"" +'hello' +""hel\ +lo"" +""\"""" +'\'' +'\\' +""\\"" +'\b' +'\f' +'\n' +'\r' +'\t' +'\v' +'\x42' +'\u0042' +'\Z' +"; + CheckAst( + ParseCode(strings), + CheckBlock( + CheckConstantStmts( + "hello", "hello", "hello", "\"", "'", + "\\", "\\", + "\b", "\f", "\n", "\r", "\t", "\v", + "\x42", "\u0042", + "Z" + ) + ) + ); + } + + /// + /// RegularExpressionLiteral :: + /// / RegularExpressionBody / RegularExpressionFlags + /// RegularExpressionBody :: + /// RegularExpressionFirstChar RegularExpressionChars + /// RegularExpressionChars :: + /// [empty] + /// RegularExpressionChars RegularExpressionChar + /// RegularExpressionFirstChar :: + /// RegularExpressionNonTerminator but not one of * or \ or / or [ + /// RegularExpressionBackslashSequence + /// RegularExpressionClass + /// RegularExpressionChar :: + /// RegularExpressionNonTerminator but not one of \ or / or [ + /// RegularExpressionBackslashSequence + /// RegularExpressionClass + /// RegularExpressionBackslashSequence :: + /// \ RegularExpressionNonTerminator + /// RegularExpressionNonTerminator :: + /// SourceCharacter but not LineTerminator + /// RegularExpressionClass :: + /// [ RegularExpressionClassChars ] + /// RegularExpressionClassChars :: + /// [empty] + /// RegularExpressionClassChars RegularExpressionClassChar + /// RegularExpressionClassChar :: + /// RegularExpressionNonTerminator but not one of ] or \ + /// RegularExpressionBackslashSequence + /// RegularExpressionFlags :: + /// [empty] + /// RegularExpressionFlags IdentifierPart + /// + [TestMethod, Priority(0)] + public void TestRegexLiterals() { + // TODO: More test cases + const string regexs = @" +/abc/; +/abc/i; +/[abc]/; +/(?:)/ +"; + CheckAst( + ParseCode(regexs), + CheckBlock( + CheckRegExs( + "abc", + new RegEx("abc", "i"), + "[abc]", + "(?:)" + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestVariableDeclaration() { + const string code = @" +var i = 0, j = 1; +"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckVar( + CheckVarDecl("i", Zero), + CheckVarDecl("j", One) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestVariableDeclarationKeyword() { + const string code = @" +var get = 0;"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckVar( + CheckVarDecl("get", Zero) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestVariableDeclarationError() { + const string code = @" +var function = 0;"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(6, 8)) + ), + CheckBlock( + CheckVar( + CheckVarDecl("function", Zero) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestVariableDeclarationError2() { + const string code = @" +var 42 = 0;"; + CheckAst( + ParseCode( + code, + new ErrorInfo(JSError.NoIdentifier, true, new IndexSpan(6, 2)) + ), + CheckBlock( + CheckVar(), + CheckExprStmt(CheckConstant(42.0)), + CheckExprStmt(CheckAssign(CheckConstant(42.0), Zero)) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestEmptyStatement() { + const string code = @" +; +"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckEmptyStmt() + ) + ); + } + + [TestMethod, Priority(0)] + public void TestIfStatement() { + const string code = @" +if(true) { + 1 +}else{ + 2 +} + +if(true) { + 1 +} + +"; + CheckAst( + ParseCode(code), + CheckBlock( + CheckIfStmt( + True, + CheckBlock(CheckConstantStmt(One)), + CheckBlock(CheckConstantStmt(Two)) + ), + CheckIfStmt( + True, + CheckBlock(CheckConstantStmt(One)) + ) + ) + ); + } + + + [TestMethod, Priority(0)] + public void TestForStatement() { + const string ForCode = @" +for(var i = 0; i<4; i++) { } +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForStatementLexical() { + const string ForCode = @" +for(const i = 0; false; ) { } +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckForStmt( + CheckLexicalDecl(CheckVarDecl("i", Zero)), + CheckConstant(false), + IsNullExpr, + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForNoVarStatement() { + const string ForCode = @" +for(i = 0; i<4; i++) { } +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckForStmt( + CheckExprStmt( + CheckBinary( + JSToken.Assign, + CheckLookup("i"), + Zero + ) + ), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForInStatement() { + const string ForCode = @" +for(var i in []) { } +for(i in []) { } +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckForInStmt( + CheckVar(CheckVarDecl("i")), + CheckArrayLiteral(), + CheckBlock() + ), + CheckForInStmt( + CheckExprStmt(CheckLookup("i")), + CheckArrayLiteral(), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForContinueStatement() { + const string ForCode = @" +for(var i = 0; i<4; i++) { continue; } +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckContinue() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForBreakStatement() { + const string ForCode = @" +for(var i = 0; i<4; i++) { break; } +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckBreak() + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForContinueLabelStatement() { + const string ForCode = @" +for(var i = 0; i<4; i++) { continue myLabel; } +myLabel: +"; + CheckAst( + ParseCode( + ForCode, + new ErrorInfo(JSError.NoLabel, true, new IndexSpan(38, 7)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckContinue("myLabel") + ) + ), + CheckLabel("myLabel") + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForContinueLabelStatement2() { + const string ForCode = @" +for(var i = 0; i<4; i++) { +myLabel: +continue myLabel; +} +"; + CheckAst( + ParseCode( + ForCode, + new ErrorInfo(JSError.BadContinue, true, new IndexSpan(41, 16)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckLabel("myLabel", CheckContinue("myLabel")) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForContinueLabelStatement3() { + const string ForCode = @" +for(var j = 0; j<4; j++) { + myLabel: + for(var i = 0; i<4; i++) { + continue myLabel; + } +} +"; + CheckAst( + ParseCode( + ForCode + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("j", Zero)), + CheckLessThan(J, Four), + CheckIncrement(J), + CheckBlock( + CheckLabel( + "myLabel", + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckContinue("myLabel") + ) + ) + ) + ) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestForBreakLabelStatement() { + const string ForCode = @" +for(var i = 0; i<4; i++) { break myLabel; } +myLabel: +"; + CheckAst( + ParseCode( + ForCode, + new ErrorInfo(JSError.NoLabel, true, new IndexSpan(35, 7)) + ), + CheckBlock( + CheckForStmt( + CheckVar(CheckVarDecl("i", Zero)), + CheckLessThan(I, Four), + CheckIncrement(I), + CheckBlock( + CheckBreak("myLabel") + ) + ), + CheckLabel("myLabel") + ) + ); + } + + [TestMethod, Priority(0)] + public void TryStatement() { + const string ForCode = @" +try { +}catch(err) { +} +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckTryCatch( + CheckBlock(), + CheckParameterDeclaration("err"), + CheckBlock() + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void WhileStatement() { + const string ForCode = @" +while(i<0) { 2; } +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckWhileStmt( + CheckLessThan(I, Zero), + CheckBlock(CheckConstantStmt(Two)) + ) + ) + ); + } + + [TestMethod, Priority(0)] + public void TestUseStrictDirective() { + const string code = @"'use strict'; +42"; + + CheckAst( + ParseCode(code), + CheckBlock( + CheckExprStmt( + CheckDirectivePrologue("use strict", true) + ), + CheckConstantStmt(42.0) + ) + ); + } + + [TestMethod, Priority(0)] + public void DoWhileStatement() { + const string ForCode = @" +do { + 2 +}while(i<1); +"; + CheckAst( + ParseCode(ForCode), + CheckBlock( + CheckDoWhileStmt( + CheckBlock(CheckConstantStmt(Two)), + CheckLessThan(I, One) + ) + ) + ); + } + + #region Checker helpers + + private static Action Pass = CheckEmptyStmt(); + private static Action Zero = CheckConstant(0.0); + private static Action One = CheckConstant(1.0); + private static Action Two = CheckConstant(2.0); + private static Action Three = CheckConstant(3.0); + private static Action Four = CheckConstant(4.0); + private static Action Null = CheckConstant(null); + private static Action True = CheckConstant(true); + private static Action False = CheckConstant(false); + private static Action I = CheckLookup("i"); + private static Action J = CheckLookup("j"); + + private static Action IsNullExpr = expr => Assert.IsNull(expr); + private static Action IsNullStmt = stmt => Assert.IsNull(stmt); + + private Action CheckIfStmt(Action condition, Action trueBlock, Action falseBlock = null) { + return stmt => { + Assert.AreEqual(typeof(IfNode), stmt.GetType()); + + var ifNode = (IfNode)stmt; + condition(ifNode.Condition); + trueBlock(ifNode.TrueBlock); + if (falseBlock != null) { + falseBlock(ifNode.FalseBlock); + } else { + Assert.AreEqual(null, ifNode.FalseBlock); + } + }; + } + + private Action CheckTryCatch(Action tryBody, Action name, Action catchBody) { + return stmt => { + Assert.AreEqual(typeof(TryNode), stmt.GetType()); + + var tryNode = (TryNode)stmt; + tryBody(tryNode.TryBlock); + name(tryNode.CatchParameter); + catchBody(tryNode.CatchBlock); + }; + } + + private Action CheckTryFinally(Action tryBody, Action finallyBody) { + return stmt => { + Assert.AreEqual(typeof(TryNode), stmt.GetType()); + + var tryNode = (TryNode)stmt; + tryBody(tryNode.TryBlock); + Assert.IsNull(tryNode.CatchBlock); + Assert.IsNull(tryNode.CatchParameter); + finallyBody(tryNode.FinallyBlock); + }; + } + + private Action CheckParameterDeclaration(string name) { + return decl => { + Assert.AreEqual(typeof(ParameterDeclaration), decl.GetType()); + + var paramDecl = (ParameterDeclaration)decl; + Assert.AreEqual(name, paramDecl.Name); + }; + } + + private Action CheckWhileStmt(Action condition, Action body) { + return stmt => { + Assert.AreEqual(typeof(WhileNode), stmt.GetType()); + + var whileStmt = (WhileNode)stmt; + condition(whileStmt.Condition); + body(whileStmt.Body); + }; + } + + private Action CheckDoWhileStmt(Action body, Action condition) { + return stmt => { + Assert.AreEqual(typeof(DoWhile), stmt.GetType()); + var whileStmt = (DoWhile)stmt; + + body(whileStmt.Body); + condition(whileStmt.Condition); + }; + } + + private static Action CheckEmptyStmt() { + return expr => { + Assert.AreEqual(typeof(EmptyStatement), expr.GetType()); + }; + } + + private static Action CheckLessThan(Action operand1, Action operand2) { + return CheckBinary(JSToken.LessThan, operand1, operand2); + } + + private static Action CheckAssign(Action operand1, Action operand2) { + return CheckBinary(JSToken.Assign, operand1, operand2); + } + + private static Action CheckBinary(JSToken token, Action operand1, Action operand2) { + return expr => { + Assert.AreEqual(typeof(BinaryOperator), expr.GetType()); + var bin = (BinaryOperator)expr; + Assert.AreEqual(token, bin.OperatorToken); + operand1(bin.Operand1); + operand2(bin.Operand2); + }; + } + + private static Action CheckBinaryStmt(JSToken token, Action operand1, Action operand2) { + return expr => CheckExprStmt(CheckBinary(token, operand1, operand2)); + } + + private static Action CheckIncrement(Action operand, bool isPostFix = true) { + return expr => CheckUnary(JSToken.Increment, operand, isPostFix); + } + + private static Action CheckUnaryStmt(JSToken token, Action operand, bool isPostFix = false) { + return expr => CheckExprStmt(CheckUnary(token, operand, isPostFix)); + } + + private static Action CheckUnary(JSToken token, Action operand, bool isPostFix = false) { + return expr => { + Assert.AreEqual(typeof(UnaryOperator), expr.GetType()); + var bin = (UnaryOperator)expr; + operand(bin.Operand); + }; + } + + private static Action CheckLookup(string name) { + return expr => { + Assert.AreEqual(typeof(Lookup), expr.GetType()); + var nameExpr = (Lookup)expr; + Assert.AreEqual(nameExpr.Name, name); + }; + } + + private static Action CheckCall(Action function, params Action[] args) { + return expr => { + Assert.AreEqual(typeof(CallNode), expr.GetType()); + + + var callNode = (CallNode)expr; + function(callNode.Function); + + Assert.IsFalse(callNode.IsConstructor); + Assert.IsFalse(callNode.InBrackets); + + Assert.AreEqual(args.Length, callNode.Arguments.Length); + for (int i = 0; i < args.Length; i++) { + args[i](callNode.Arguments[i]); + } + }; + } + + private static Action CheckIndex(Action function, params Action[] args) { + return expr => { + Assert.AreEqual(typeof(CallNode), expr.GetType()); + + + var callNode = (CallNode)expr; + function(callNode.Function); + + Assert.IsFalse(callNode.IsConstructor); + Assert.IsTrue(callNode.InBrackets); + + Assert.AreEqual(args.Length, callNode.Arguments.Length); + for (int i = 0; i < args.Length; i++) { + args[i](callNode.Arguments[i]); + } + }; + } + + private static Action CheckMember(string name, Action root) { + return expr => { + Assert.AreEqual(typeof(Member), expr.GetType()); + + var member = (Member)expr; + Assert.AreEqual(name, member.Name); + root(member.Root); + }; + } + + + private static Action CheckLookupStmt(string name) { + return stmt => CheckExprStmt(CheckLookup(name)); + } + + private static Action CheckVar(params Action[] decls) { + return expr => { + Assert.AreEqual(typeof(Var), expr.GetType()); + var varNode = (Var)expr; + Assert.AreEqual(varNode.Count, decls.Length); + for (int i = 0; i < decls.Length; i++) { + decls[i](varNode[i]); + } + }; + } + + private static Action CheckVarDecl(string name, Action initializer = null) { + return decl => { + Assert.AreEqual(decl.Name, name); + if (initializer != null) { + initializer(decl.Initializer); + } else { + Assert.AreEqual(null, decl.Initializer); + } + }; + } + + private static Action CheckLexicalDecl(params Action[] decls) { + return expr => { + Assert.AreEqual(typeof(LexicalDeclaration), expr.GetType()); + var varNode = (LexicalDeclaration)expr; + Assert.AreEqual(varNode.Count, decls.Length); + for (int i = 0; i < decls.Length; i++) { + decls[i](varNode[i]); + } + }; + } + + + private static Action CheckConstant(object value) { + return expr => { + Assert.AreEqual(typeof(ConstantWrapper), expr.GetType()); + + Assert.AreEqual(value, ((ConstantWrapper)expr).Value); + }; + } + + private static Action CheckConstantStmt(object value) { + return stmt => CheckExprStmt(CheckConstant(value)); + } + + private static Action[] CheckConstantStmts(params object[] value) { + return value.Select(x => CheckConstantStmt(x)).ToArray(); + } + + class RegEx { + public readonly string Pattern; + public readonly string Switches; + + public RegEx(string pattern, string switches) { + Pattern = pattern; + Switches = switches; + } + + public static implicit operator RegEx(string pattern) { + return new RegEx(pattern, null); + } + } + + private static Action[] CheckRegExs(params RegEx[] values) { + return values.Select(x => CheckRegExStmt(x.Pattern, x.Switches)).ToArray(); + } + + private static Action CheckRegExStmt(string pattern, string switches = null) { + return stmt => CheckExprStmt(CheckRegEx(pattern, switches)); + } + + private static Action CheckRegEx(string pattern, string switches = null) { + return expr => { + Assert.AreEqual(typeof(RegExpLiteral), expr.GetType()); + + var regex = (RegExpLiteral)expr; + Assert.AreEqual(pattern, regex.Pattern); + Assert.AreEqual(switches, regex.PatternSwitches); + }; + } + + private static Action CheckYieldExpr(Action operand) { + return CheckYieldExpr(operand, false); + } + + private static Action CheckYieldExpr(Action operand, bool isYieldFrom) { + return expr => { + Assert.AreEqual(typeof(YieldExpression), expr.GetType()); + + var yield = (YieldExpression)expr; + if (operand != null) { + operand(yield.Operand); + } else { + Assert.IsNull(yield.Operand); + } + Assert.AreEqual(isYieldFrom, yield.YieldFrom); + }; + } + + private static Action CheckExprStmt(Action expr) { + return stmt => { + Assert.AreEqual(typeof(ExpressionStatement), stmt.GetType()); + + var exprStmt = (ExpressionStatement)stmt; + expr(exprStmt.Expression); + }; + } + + private static Action CheckForStmt(Action init, Action condition, Action increment, Action body) { + return stmt => { + Assert.AreEqual(typeof(ForNode), stmt.GetType()); + ForNode forStmt = (ForNode)stmt; + + init(forStmt.Initializer); + condition(forStmt.Condition); + increment(forStmt.Incrementer); + body(forStmt.Body); + }; + } + + private static Action CheckBlock(params Action[] statements) { + return stmt => { + Assert.AreEqual(typeof(Block), stmt.GetType()); + var block = (Block)stmt; + Assert.AreEqual(statements.Length, block.Count, "Statement Count not Equal to found in Block"); + for (int i = 0; i < block.Count; i++) { + try { + statements[i](block[i]); + } catch (AssertFailedException e) { + throw new AssertFailedException(String.Format("Block Item {0}: {1}", i, e.Message + Environment.NewLine + e.StackTrace.ToString()), e); + } + } + }; + } + + private static Action CheckDebuggerStmt() { + return stmt => { + Assert.AreEqual(typeof(DebuggerNode), stmt.GetType()); + }; + } + + private static Action CheckContinue(string label = null) { + return stmt => { + Assert.AreEqual(typeof(ContinueNode), stmt.GetType()); + + var contStmt = (ContinueNode)stmt; + if (label != null) { + Assert.AreEqual(label, contStmt.Label); + } else { + Assert.AreEqual(null, contStmt.Label); + } + }; + } + private static Action CheckBreak(string label = null) { + return stmt => { + Assert.AreEqual(typeof(Break), stmt.GetType()); + + var breakStmt = (Break)stmt; + if (label != null) { + Assert.AreEqual(label, breakStmt.Label); + } else { + Assert.AreEqual(null, breakStmt.Label); + } + }; + } + + private static Action CheckLabel(string label, Action statement = null) { + return stmt => { + Assert.AreEqual(typeof(LabeledStatement), stmt.GetType()); + + var labelStmt = (LabeledStatement)stmt; + Assert.AreEqual(label, labelStmt.Label); + if (statement != null) { + statement(labelStmt.Statement); + } else { + Assert.AreEqual(null, labelStmt.Statement); + } + }; + } + + private static Action CheckSwitch(Action expr, params Action[] cases) { + return stmt => { + Assert.AreEqual(typeof(Switch), stmt.GetType()); + + var switchStmt = (Switch)stmt; + expr(switchStmt.Expression); + Assert.AreEqual(cases.Length, switchStmt.Cases.Length); + for (int i = 0; i < cases.Length; i++) { + cases[i](switchStmt.Cases[i]); + } + }; + } + + private static Action CheckCase(Action expr, Action body) { + return stmt => { + Assert.AreEqual(typeof(SwitchCase), stmt.GetType()); + + var switchCase = (SwitchCase)stmt; + if (expr != null) { + expr(switchCase.CaseValue); + } else { + Assert.IsNull(switchCase.CaseValue, "expected default case"); + } + body(switchCase.Statements); + }; + } + + private Action CheckThrow(Action action = null) { + return stmt => { + Assert.AreEqual(typeof(ThrowNode), stmt.GetType()); + + var throwNode = (ThrowNode)stmt; + if (action != null) { + action(throwNode.Operand); + } else { + Assert.IsNull(throwNode.Operand, "expected no throw operand"); + } + }; + } + + private Action CheckWith(Action expr, Action body) { + return stmt => { + Assert.AreEqual(typeof(WithNode), stmt.GetType()); + + var withNode = (WithNode)stmt; + expr(withNode.WithObject); + body(withNode.Body); + }; + } + + private Action CheckConditional(Action condition, Action ifTrue, Action ifFalse) { + return expr => { + Assert.AreEqual(typeof(Conditional), expr.GetType()); + + var cond = (Conditional)expr; + condition(cond.Condition); + ifTrue(cond.TrueExpression); + ifFalse(cond.FalseExpression); + }; + } + + private Action CheckGrouping(Action operand) { + return expr => { + Assert.AreEqual(typeof(GroupingOperator), expr.GetType()); + + var grouping = (GroupingOperator)expr; + operand(grouping.Operand); + }; + } + + private Action CheckArrayLiteral(params Action[] values) { + return expr => { + Assert.AreEqual(typeof(ArrayLiteral), expr.GetType()); + + var array = (ArrayLiteral)expr; + Assert.AreEqual(values.Length, array.Elements.Length); + for (int i = 0; i < values.Length; i++) { + values[i](array.Elements[i]); + } + }; + } + + private Action CheckObjectLiteral(params Action[] values) { + return expr => { + Assert.AreEqual(typeof(ObjectLiteral), expr.GetType()); + + var array = (ObjectLiteral)expr; + Assert.AreEqual(values.Length, array.Properties.Length); + for (int i = 0; i < values.Length; i++) { + values[i](array.Properties[i]); + } + }; + } + + private Action Property(object name, Action value) { + return expr => { + Assert.AreEqual(typeof(ObjectLiteralProperty), expr.GetType()); + + var prop = (ObjectLiteralProperty)expr; + Assert.AreEqual(name, prop.Name.Value); + value(prop.Value); + }; + } + + private Action GetterSetter(bool isGetter, string name, Action value) { + return expr => { + Assert.AreEqual(typeof(ObjectLiteralProperty), expr.GetType()); + + var prop = (ObjectLiteralProperty)expr; + value(prop.Value); + + Assert.AreEqual(typeof(GetterSetter), prop.Name.GetType()); + var getterSetter = (GetterSetter)prop.Name; + Assert.AreEqual(name, prop.Name.Value); + Assert.AreEqual(isGetter, getterSetter.IsGetter); + }; + } + + private Action CheckFunctionObject(string name, Action body, params Action[] args) { + return CheckFunctionObject(name, body, false, args); + } + + private Action CheckFunctionObject(string name, Action body, bool isGenerator, params Action[] args) { + return stmt => { + Assert.AreEqual(typeof(FunctionObject), stmt.GetType()); + + var func = (FunctionObject)stmt; + Assert.AreEqual(name, func.Name); + Assert.AreEqual(isGenerator, func.IsGenerator); + + body(func.Body); + if (func.ParameterDeclarations != null) { + Assert.AreEqual(args.Length, func.ParameterDeclarations.Length); + for (int i = 0; i < func.ParameterDeclarations.Length; i++) { + args[i](func.ParameterDeclarations[i]); + } + } else { + Assert.IsNull(args); + } + }; + } + + private Action CheckFunctionExpr(Action functionObject) { + return expr => { + Assert.AreEqual(typeof(FunctionExpression), expr.GetType()); + + var funcExpr = (FunctionExpression)expr; + + functionObject(funcExpr.Function); + }; + } + + private Action CheckForInStmt(Action decl, Action collection, Action body) { + return stmt => { + Assert.AreEqual(typeof(ForIn), stmt.GetType()); + + var forIn = (ForIn)stmt; + decl(forIn.Variable); + collection(forIn.Collection); + }; + } + private Action CheckReturn(Action operand = null) { + return stmt => { + Assert.AreEqual(typeof(ReturnNode), stmt.GetType()); + + var ret = (ReturnNode)stmt; + if (ret.Operand == null) { + Assert.IsNull(operand); + } else { + Assert.IsNotNull(operand); + operand(ret.Operand); + } + }; + } + + + private Action CheckDirectivePrologue(string value, bool useStrict = false) { + return expr => { + Assert.AreEqual(typeof(DirectivePrologue), expr.GetType()); + + Assert.AreEqual(value, ((DirectivePrologue)expr).Value); + Assert.AreEqual(useStrict, ((DirectivePrologue)expr).UseStrict); + }; + } + + #endregion + + private static JsAst ParseCode(string code, params ErrorInfo[] errors) { + return ParseCode(code, false, errors); + } + + private static JsAst ParseCode(string code, bool collectWarnings, params ErrorInfo[] errors) { + CollectingErrorSink errorSink = new CollectingErrorSink(collectWarnings); + + var parser = new JSParser(code, errorSink); + var ast = parser.Parse(new CodeSettings()); + + if (errors != null) { + errorSink.CheckErrors(errors); + } + return ast; + } + + private static JsAst ParseExpression(string code, bool collectWarnings, params ErrorInfo[] errors) { + CollectingErrorSink errorSink = new CollectingErrorSink(collectWarnings); + + var parser = new JSParser(code, errorSink); + var ast = parser.Parse(new CodeSettings() { SourceMode = JavaScriptSourceMode.Expression }); + + errorSink.CheckErrors(errors); + return ast; + } + + private void CheckAst(JsAst ast, Action checkBody) { + checkBody(ast.Block); + + ast.Walk(new TestVisitor()); + + var newAst = SerializationTests.RoundTrip(ast); + + checkBody(newAst.Block); + newAst.Walk(new TestVisitor()); + } + + class TestVisitor : AstVisitor { + private void TestNode(Node node) { + if (node != null) { + Assert.IsNotNull(node.ToString()); + foreach (var child in node.Children) { + Assert.IsNotNull(child); + } + + IEnumerable enumerable = node as IEnumerable; + if (enumerable != null) { + foreach (var value in enumerable) { + Assert.IsNotNull(value); + } + } + } + } + + private void TestNodes(T[] nodes) where T : Node { + if (nodes != null) { + foreach (var node in nodes) { + Assert.IsNotNull(node); + TestNode(node); + } + } + } + + public override bool Walk(ArrayLiteral node) { TestNode(node); TestNodes(node.Elements); return base.Walk(node); } + public override bool Walk(BinaryOperator node) { TestNode(node); return base.Walk(node); } + public override bool Walk(CommaOperator node) { TestNode(node); return base.Walk(node); } + public override bool Walk(Block node) { TestNode(node); return base.Walk(node); } + public override bool Walk(Break node) { TestNode(node); return base.Walk(node); } + public override bool Walk(CallNode node) { TestNode(node); TestNodes(node.Arguments); return base.Walk(node); } + public override bool Walk(Conditional node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ConstantWrapper node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ConstStatement node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ContinueNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(DebuggerNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(DirectivePrologue node) { TestNode(node); return base.Walk(node); } + public override bool Walk(DoWhile node) { TestNode(node); return base.Walk(node); } + public override bool Walk(EmptyStatement node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ForIn node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ForNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(FunctionObject node) { TestNode(node); TestNodes(node.ParameterDeclarations); return base.Walk(node); } + public override bool Walk(GetterSetter node) { TestNode(node); return base.Walk(node); } + public override bool Walk(GroupingOperator node) { TestNode(node); return base.Walk(node); } + public override bool Walk(IfNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(LabeledStatement node) { TestNode(node); return base.Walk(node); } + public override bool Walk(LexicalDeclaration node) { TestNode(node); return base.Walk(node); } + public override bool Walk(Lookup node) { TestNode(node); return base.Walk(node); } + public override bool Walk(Member node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ObjectLiteral node) { TestNode(node); TestNodes(node.Properties); return base.Walk(node); } + public override bool Walk(ObjectLiteralField node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ObjectLiteralProperty node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ParameterDeclaration node) { TestNode(node); return base.Walk(node); } + public override bool Walk(RegExpLiteral node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ReturnNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(Switch node) { TestNode(node); TestNodes(node.Cases); return base.Walk(node); } + public override bool Walk(SwitchCase node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ThisLiteral node) { TestNode(node); return base.Walk(node); } + public override bool Walk(ThrowNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(TryNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(Var node) { TestNode(node); return base.Walk(node); } + public override bool Walk(VariableDeclaration node) { TestNode(node); return base.Walk(node); } + public override bool Walk(UnaryOperator node) { TestNode(node); return base.Walk(node); } + public override bool Walk(WhileNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(WithNode node) { TestNode(node); return base.Walk(node); } + public override bool Walk(JsAst jsAst) { TestNode(jsAst); return base.Walk(jsAst); } + public override bool Walk(FunctionExpression functionExpression) { TestNode(functionExpression); return base.Walk(functionExpression); } + public override bool Walk(ExpressionStatement node) { TestNode(node); return base.Walk(node); } + } + } + + class ErrorInfo { + public readonly bool IsError; + public readonly JSError Error; + public readonly IndexSpan Span; + + public ErrorInfo(JSError error, bool isError, IndexSpan span) { + Error = error; + IsError = isError; + Span = span; + } + + public override string ToString() { + return String.Format("{0}: JSError.{1} {2}", + IsError ? "Error" : "Warning", + Error, + Span + ); + } + } + + class CollectingErrorSink : ErrorSink { + private readonly List _errors = new List(); + public bool _collectWarnings; + + public CollectingErrorSink(bool collectWarnings = false) { + _collectWarnings = collectWarnings; + } + + public override void OnError(JScriptExceptionEventArgs error) { + error.Error.ToString(); + var result = new ErrorInfo(error.Error.ErrorCode, error.Error.IsError, error.Exception.Span); + + if (error.Error.IsError || _collectWarnings) { + _errors.Add(result); + } + } + + public void CheckErrors(ErrorInfo[] errors) { + bool success = false; + try { + Assert.AreEqual(errors.Length, Errors.Count, "Unexpected Error Count"); + for (int i = 0; i < errors.Length; i++) { + Assert.AreEqual(errors[i].ToString(), Errors[i].ToString()); + } + success = true; + } finally { + if (!success) { + foreach (var error in Errors) { + Console.WriteLine( + "new ErrorInfo(JSError.{0}, {1}, new IndexSpan({2}, {3})),", + error.Error, + error.IsError ? "true" : "false", + error.Span.Start, + error.Span.Length + ); + } + } + } + } + + public List Errors { + get { + return _errors; + } + } + } +} diff --git a/Nodejs/Tests/Core.UI/BraceCompletion.cs b/Nodejs/Tests/Core.UI/BraceCompletion.cs index 5f553cc06..ad44e5a56 100644 --- a/Nodejs/Tests/Core.UI/BraceCompletion.cs +++ b/Nodejs/Tests/Core.UI/BraceCompletion.cs @@ -18,8 +18,8 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using TestUtilities; using TestUtilities.SharedProject; -using TestUtilities.UI; - +using TestUtilities.UI; + namespace Microsoft.Nodejs.Tests.UI { [TestClass] public class BraceCompletion : NodejsProjectTest { diff --git a/Nodejs/Tests/Core.UI/NodejsBasicProjectTests.cs b/Nodejs/Tests/Core.UI/NodejsBasicProjectTests.cs index 952adf9a7..146c732d8 100644 --- a/Nodejs/Tests/Core.UI/NodejsBasicProjectTests.cs +++ b/Nodejs/Tests/Core.UI/NodejsBasicProjectTests.cs @@ -1,522 +1,522 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading; -using System.Windows.Automation; -using EnvDTE; -using Microsoft.NodejsTools; -using Microsoft.NodejsTools.Project; -using Microsoft.VisualStudio.Language.Intellisense; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.VisualStudioTools; -using Microsoft.VisualStudioTools.VSTestHost; -using TestUtilities; -using TestUtilities.UI; -using TestUtilities.Nodejs; -using Key = System.Windows.Input.Key; -using MouseButton = System.Windows.Input.MouseButton; - -namespace Microsoft.Nodejs.Tests.UI { - [TestClass] - public class NodejsBasicProjectTests : NodejsProjectTest { - [ClassInitialize] - public static void DoDeployment(TestContext context) { - AssertListener.Initialize(); - NodejsTestData.Deploy(); - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AddNewTypeScriptItem() { - using (var solution = Project("AddNewTypeScriptItem", Compile("server")).Generate().ToVs()) { - var project = solution.WaitForItem("AddNewTypeScriptItem", "server.js"); - AutomationWrapper.Select(project); - - using (var newItem = solution.AddNewItem()) { - newItem.FileName = "NewTSFile.ts"; - newItem.OK(); - } - - using (AutoResetEvent buildDone = new AutoResetEvent(false)) { - VSTestContext.DTE.Events.BuildEvents.OnBuildDone += (sender, args) => { - buildDone.Set(); - }; - - solution.ExecuteCommand("Build.BuildSolution"); - solution.WaitForOutputWindowText("Build", "tsc.exe"); - Assert.IsTrue(buildDone.WaitOne(10000), "failed to wait for build)"); - } - } - } - - /// - /// https://nodejstools.codeplex.com/workitem/1195 - /// - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestExcludedErrors() { - var project = Project("TestExcludedErrors", - Compile("server", "function f(a, b, c) { }\r\n\r\n"), - Compile("excluded", "aa bb", isExcluded: true) - ); - - using (var solution = project.Generate().ToVs()) { - List allItems = solution.WaitForErrorListItems(0); - Assert.AreEqual(0, allItems.Count); - - var excluded = solution.WaitForItem("TestExcludedErrors", "excluded.js"); - AutomationWrapper.Select(excluded); - solution.ExecuteCommand("Project.IncludeInProject"); - - allItems = solution.WaitForErrorListItems(1); - Assert.AreEqual(1, allItems.Count); - - excluded = solution.WaitForItem("TestExcludedErrors", "excluded.js"); - AutomationWrapper.Select(excluded); - solution.ExecuteCommand("Project.ExcludeFromProject"); - - allItems = solution.WaitForErrorListItems(0); - Assert.AreEqual(0, allItems.Count); - } - } - - - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestDebuggerPort() { - var filename = Path.Combine(TestData.GetTempPath(), Path.GetRandomFileName()); - Console.WriteLine("Temp file is: {0}", filename); - var code = String.Format(@" -require('fs').writeFileSync('{0}', process.debugPort); -while(true) {{ -}}", filename.Replace("\\", "\\\\")); - - var project = Project("DebuggerPort", - Compile("server", code), - Property(NodejsConstants.DebuggerPort, "1234"), - Property(CommonConstants.StartupFile, "server.js") - ); - - using (var solution = project.Generate().ToVs()) { - solution.ExecuteCommand("Debug.Start"); - solution.WaitForMode(dbgDebugMode.dbgRunMode); - - for (int i = 0; i < 10 && !File.Exists(filename); i++) { - System.Threading.Thread.Sleep(1000); - } - Assert.IsTrue(File.Exists(filename), "debugger port not written out"); - solution.ExecuteCommand("Debug.StopDebugging"); - - Assert.AreEqual( - File.ReadAllText(filename), - "1234" - ); - } - } - - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestEnvironmentVariables() { - var filename = Path.Combine(TestData.GetTempPath(), Path.GetRandomFileName()); - Console.WriteLine("Temp file is: {0}", filename); - var code = String.Format(@" -require('fs').writeFileSync('{0}', process.env.fob + process.env.bar + process.env.baz); -while(true) {{ -}}", filename.Replace("\\", "\\\\")); - - var project = Project("EnvironmentVariables", - Compile("server", code), - Property(NodejsConstants.Environment, "fob=1\nbar=2;3\r\nbaz=4"), - Property(CommonConstants.StartupFile, "server.js") - ); - - using (var solution = project.Generate().ToVs()) { - solution.ExecuteCommand("Debug.Start"); - solution.WaitForMode(dbgDebugMode.dbgRunMode); - - for (int i = 0; i < 10 && !File.Exists(filename); i++) { - System.Threading.Thread.Sleep(1000); - } - Assert.IsTrue(File.Exists(filename), "environment variables not written out"); - solution.ExecuteCommand("Debug.StopDebugging"); - - Assert.AreEqual( - File.ReadAllText(filename), - "12;34" - ); - } - } - - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestEnvironmentVariablesNoDebugging() { - var filename = Path.Combine(TestData.GetTempPath(), Path.GetRandomFileName()); - Console.WriteLine("Temp file is: {0}", filename); - var code = String.Format(@" -require('fs').writeFileSync('{0}', process.env.fob + process.env.bar + process.env.baz); -", filename.Replace("\\", "\\\\")); - - var project = Project("EnvironmentVariables", - Compile("server", code), - Property(NodejsConstants.Environment, "fob=1\nbar=2;3\r\nbaz=4"), - Property(CommonConstants.StartupFile, "server.js") - ); - - using (var solution = project.Generate().ToVs()) { - solution.ExecuteCommand("Debug.StartWithoutDebugging"); - - for (int i = 0; i < 10 && !File.Exists(filename); i++) { - System.Threading.Thread.Sleep(1000); - } - Assert.IsTrue(File.Exists(filename), "environment variables not written out"); - - Assert.AreEqual( - File.ReadAllText(filename), - "12;34" - ); - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestProjectProperties() { - var filename = Path.Combine(TestData.GetTempPath(), Path.GetRandomFileName()); - - var project = Project("ProjectProperties", - Compile("server"), - Property(NodejsConstants.Environment, "fob=1\r\nbar=2;3\nbaz=4"), - Property(NodejsConstants.DebuggerPort, "1234"), - Property(CommonConstants.StartupFile, "server.js") - ); - - using (var solution = project.Generate().ToVs()) { - var projectNode = solution.WaitForItem("ProjectProperties"); - AutomationWrapper.Select(projectNode); - - solution.ExecuteCommand("ClassViewContextMenus.ClassViewMultiselectProjectReferencesItems.Properties"); - AutomationElement doc = null; - for (int i = 0; i < 10; i++) { - doc = ((VisualStudioInstance)solution).App.GetDocumentTab("ProjectProperties"); - if (doc != null) { - break; - } - System.Threading.Thread.Sleep(1000); - } - Assert.IsNotNull(doc, "Failed to find project properties tab"); - - var debuggerPort = - new TextBox( - new AutomationWrapper(doc).FindByAutomationId("_debuggerPort") - ); - var envVars = new TextBox( - new AutomationWrapper(doc).FindByAutomationId("_envVars") - ); - - Assert.AreEqual(debuggerPort.Value, "1234"); - Assert.AreEqual(envVars.Value, "fob=1\r\nbar=2;3\r\nbaz=4"); - - debuggerPort.Value = "2468"; - - // Multi-line text box does not support setting value via automation. - envVars.SetFocus(); - Keyboard.ControlA(); - Keyboard.Backspace(); - Keyboard.Type("fob=0\nbar=0;0\nbaz=0"); - - solution.ExecuteCommand("File.SaveAll"); - - var projFile = File.ReadAllText(solution.GetProject("ProjectProperties").FullName); - Assert.AreNotEqual(-1, projFile.IndexOf("2468")); - Assert.AreNotEqual(-1, projFile.IndexOf("fob=0\r\nbar=0;0\r\nbaz=0")); - } - } - - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestClientServerIntelliSenseModes() { - string - solutionLabel = "Solution 'ClientServerCode' (1 project)", - projectLabel = "ClientServerCode", - nodeDirectoryLabel = NodejsFolderNode.AppendLabel("NodeDirectory", FolderContentType.Node), - nodeSubDirectoryLabel = NodejsFolderNode.AppendLabel("NodeSubDirectory", FolderContentType.Node), - browserDirectoryLabel = NodejsFolderNode.AppendLabel("BrowserDirectory", FolderContentType.Browser), - emptyBrowserSubDirectoryLabel = "BrowserSubDirectory", - browserSubDirectoryLabel = NodejsFolderNode.AppendLabel("BrowserSubDirectory", FolderContentType.Browser), - mixedDirectoryLabel = NodejsFolderNode.AppendLabel("MixedDirectory", FolderContentType.Mixed), - mixedDirectoryBrowserDirectoryLabel = NodejsFolderNode.AppendLabel("BrowserDirectory", FolderContentType.Browser), - mixedDirectoryNodeDirectoryLabel = NodejsFolderNode.AppendLabel("NodeDirectory", FolderContentType.Node), - browserCodeLabel = "browserCode.js", - mixedDirectoryRenamedLabel = NodejsFolderNode.AppendLabel("MixedDirectoryRenamed", FolderContentType.Mixed); - - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\ClientServerCode\ClientServerCode.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", true)) { - // Wait until project is loaded - var solutionExplorer = app.OpenSolutionExplorer(); - - solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - "app.js"); - - var nodejsProject = app.GetProject("ClientServerCode").GetNodejsProject(); - - var projectNode = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel); - - var browserDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - browserDirectoryLabel - ); - Assert.IsNotNull( - browserDirectory, - "Browser directories should be labeled as such. Could not find " + browserDirectoryLabel); - - var browserSubDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - browserDirectoryLabel, - emptyBrowserSubDirectoryLabel - ); - Assert.IsNotNull( - browserSubDirectory, - "Project initialization: could not find " + emptyBrowserSubDirectoryLabel); - - var nodeDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - nodeDirectoryLabel - ); - Assert.IsNotNull( - nodeDirectory, - "Node directories should be labeled as such. Could not find " + nodeDirectoryLabel); - - var nodeSubDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - nodeDirectoryLabel, - nodeSubDirectoryLabel - ); - Assert.IsNotNull( - nodeSubDirectory, - "Project initialization: could not find " + nodeSubDirectoryLabel); - - projectNode.Select(); - using (var newItem = NewItemDialog.FromDte(app)) { - newItem.FileName = "newItem.js"; - newItem.OK(); - } - - Assert.AreEqual( - "Compile", - nodejsProject.GetItemType("newItem.js"), - "Top level files should be set to item type 'Compile'"); - - Keyboard.Type("process."); - Keyboard.Type(Keyboard.CtrlSpace.ToString()); - - using (var session = app.GetDocument(Path.Combine(nodejsProject.ProjectHome, @"newItem.js")).WaitForSession()) { - var completions = session.Session.CompletionSets.First().Completions.Select(x => x.InsertionText); - Assert.IsTrue( - completions.Contains("env"), - "New documents of the node type should open with default VS editor" - ); - } - - browserSubDirectory.Select(); - using (var newBrowserItem = NewItemDialog.FromDte(app)) { - newBrowserItem.FileName = "newBrowserItem.js"; - newBrowserItem.OK(); - } - - Keyboard.Type("document."); - System.Threading.Thread.Sleep(2000); - Keyboard.Type(Keyboard.CtrlSpace.ToString()); - - using (var session = app.GetDocument(Path.Combine(nodejsProject.ProjectHome, @"BrowserDirectory\browserSubDirectory\newBrowserItem.js")).WaitForSession()) { - var completions = session.Session.CompletionSets.First().Completions.Select(x => x.InsertionText); - Assert.IsTrue( - completions.Contains("body"), - "New documents of the browser type should open with default VS editor" - ); - } - - browserSubDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - browserDirectoryLabel, - browserSubDirectoryLabel - ); - Assert.IsNotNull( - browserSubDirectory, - "Folder label was not updated to " + browserSubDirectoryLabel); - - var newBrowserItemFile = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - browserDirectoryLabel, - browserSubDirectoryLabel, - "newBrowserItem.js" - ); - Assert.AreEqual( - "Content", - nodejsProject.GetItemType(@"BrowserDirectory\BrowserSubDirectory\newBrowserItem.js"), - "Adding a javascript file to a 'browser' directory should set the item type as Content."); - - var mixedDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - mixedDirectoryLabel - ); - Assert.IsNotNull( - mixedDirectory, - "Folder with mixed browser/node content should be specified as such. Could not find: " + mixedDirectoryLabel); - - nodeDirectory.Select(); - using (var newTypeScriptItem = NewItemDialog.FromDte(app)) { - newTypeScriptItem.FileName = "newTypeScriptItem.ts"; - newTypeScriptItem.OK(); - } - - Assert.AreEqual( - "TypeScriptCompile", - nodejsProject.GetItemType(@"NodeDirectory\newTypeScriptItem.ts"), - "Non-javascript files should retain their content type."); - - var newBrowserItemNode = nodejsProject.FindNodeByFullPath( - Path.Combine(nodejsProject.ProjectHome, @"BrowserDirectory\BrowserSubDirectory\newBrowserItem.js") - ); - - newBrowserItemNode.ExcludeFromProject(); - var excludedBrowserItem = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - browserDirectoryLabel, - emptyBrowserSubDirectoryLabel - ); - Assert.IsNotNull( - emptyBrowserSubDirectoryLabel, - "Label should be removed when there are no included javascript files the directory. Could not find " + emptyBrowserSubDirectoryLabel); - - (newBrowserItemNode as NodejsFileNode).IncludeInProject(false); - var includedBrowserItem = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - browserDirectoryLabel, - browserSubDirectoryLabel - ); - Assert.IsNotNull( - includedBrowserItem, - "Label should be added when a javascript file is included in the directory. Could not find " + browserSubDirectoryLabel); - - var mixedDirectoryNode = app.GetProject("ClientServerCode").GetNodejsProject().FindNodeByFullPath( - Path.Combine(nodejsProject.ProjectHome, @"MixedDirectory\")); - - System.Threading.Thread.Sleep(2000); - mixedDirectory.Select(); - Keyboard.PressAndRelease(Key.F2); - Keyboard.Type("MixedDirectoryRenamed"); - Keyboard.PressAndRelease(Key.Enter); - - mixedDirectoryNode.ExpandItem(EXPANDFLAGS.EXPF_ExpandFolderRecursively); - var renamedMixedDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - mixedDirectoryRenamedLabel, - mixedDirectoryBrowserDirectoryLabel, - browserCodeLabel - ); - - Assert.IsNotNull( - renamedMixedDirectory, - "Renaming mixed directory failed: could not find " + browserCodeLabel); - - newBrowserItemNode.ItemNode.ItemTypeName = "Compile"; - - var nodeBrowserSubDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - NodejsFolderNode.AppendLabel("BrowserDirectory", FolderContentType.Mixed), - NodejsFolderNode.AppendLabel("BrowserSubDirectory", FolderContentType.Node) - ); - Assert.IsNotNull( - nodeBrowserSubDirectory, - "Changing the item type should change the directory label. Could not find " + - NodejsFolderNode.AppendLabel("BrowserSubDirectory", FolderContentType.Node) - ); - - var nodeDirectoryNode = app.GetProject("ClientServerCode").GetNodejsProject().FindNodeByFullPath( - Path.Combine(app.GetProject("ClientServerCode").GetNodejsProject().ProjectHome, - @"NodeDirectory\")); - (nodeDirectoryNode as NodejsFolderNode).SetItemTypeRecursively(VSLangProj.prjBuildAction.prjBuildActionContent); - - Assert.AreEqual( - "Content", - nodejsProject.GetItemType(@"NodeDirectory\NodeSubDirectory\nodeCode.js"), - "nodeCode.js file should be marked as content after recursively setting directory contents as Content." - ); - Assert.AreEqual( - "TypeScriptCompile", - nodejsProject.GetItemType(@"NodeDirectory\newTypeScriptItem.ts"), - "Only javascript file item types should change when marking item types recursively." - ); - - var fromPoint = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - mixedDirectoryRenamedLabel, - mixedDirectoryNodeDirectoryLabel - ).GetClickablePoint(); - - var toPoint = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - mixedDirectoryRenamedLabel, - mixedDirectoryBrowserDirectoryLabel - ).GetClickablePoint(); - - Mouse.MoveTo(fromPoint); - Mouse.Down(MouseButton.Left); - - Mouse.MoveTo(toPoint); - Mouse.Up(MouseButton.Left); - - var draggedDirectory = solutionExplorer.WaitForItem( - solutionLabel, - projectLabel, - mixedDirectoryRenamedLabel, - NodejsFolderNode.AppendLabel("BrowserDirectory", FolderContentType.Mixed), - NodejsFolderNode.AppendLabel("NodeDirectory", FolderContentType.Node) - ); - Assert.IsNotNull( - draggedDirectory, - "Labels not properly updated after dragging and dropping directory." - ); - } - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Windows.Automation; +using EnvDTE; +using Microsoft.NodejsTools; +using Microsoft.NodejsTools.Project; +using Microsoft.VisualStudio.Language.Intellisense; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudioTools; +using Microsoft.VisualStudioTools.VSTestHost; +using TestUtilities; +using TestUtilities.UI; +using TestUtilities.Nodejs; +using Key = System.Windows.Input.Key; +using MouseButton = System.Windows.Input.MouseButton; + +namespace Microsoft.Nodejs.Tests.UI { + [TestClass] + public class NodejsBasicProjectTests : NodejsProjectTest { + [ClassInitialize] + public static void DoDeployment(TestContext context) { + AssertListener.Initialize(); + NodejsTestData.Deploy(); + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AddNewTypeScriptItem() { + using (var solution = Project("AddNewTypeScriptItem", Compile("server")).Generate().ToVs()) { + var project = solution.WaitForItem("AddNewTypeScriptItem", "server.js"); + AutomationWrapper.Select(project); + + using (var newItem = solution.AddNewItem()) { + newItem.FileName = "NewTSFile.ts"; + newItem.OK(); + } + + using (AutoResetEvent buildDone = new AutoResetEvent(false)) { + VSTestContext.DTE.Events.BuildEvents.OnBuildDone += (sender, args) => { + buildDone.Set(); + }; + + solution.ExecuteCommand("Build.BuildSolution"); + solution.WaitForOutputWindowText("Build", "tsc.exe"); + Assert.IsTrue(buildDone.WaitOne(10000), "failed to wait for build)"); + } + } + } + + /// + /// https://nodejstools.codeplex.com/workitem/1195 + /// + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestExcludedErrors() { + var project = Project("TestExcludedErrors", + Compile("server", "function f(a, b, c) { }\r\n\r\n"), + Compile("excluded", "aa bb", isExcluded: true) + ); + + using (var solution = project.Generate().ToVs()) { + List allItems = solution.WaitForErrorListItems(0); + Assert.AreEqual(0, allItems.Count); + + var excluded = solution.WaitForItem("TestExcludedErrors", "excluded.js"); + AutomationWrapper.Select(excluded); + solution.ExecuteCommand("Project.IncludeInProject"); + + allItems = solution.WaitForErrorListItems(1); + Assert.AreEqual(1, allItems.Count); + + excluded = solution.WaitForItem("TestExcludedErrors", "excluded.js"); + AutomationWrapper.Select(excluded); + solution.ExecuteCommand("Project.ExcludeFromProject"); + + allItems = solution.WaitForErrorListItems(0); + Assert.AreEqual(0, allItems.Count); + } + } + + + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestDebuggerPort() { + var filename = Path.Combine(TestData.GetTempPath(), Path.GetRandomFileName()); + Console.WriteLine("Temp file is: {0}", filename); + var code = String.Format(@" +require('fs').writeFileSync('{0}', process.debugPort); +while(true) {{ +}}", filename.Replace("\\", "\\\\")); + + var project = Project("DebuggerPort", + Compile("server", code), + Property(NodejsConstants.DebuggerPort, "1234"), + Property(CommonConstants.StartupFile, "server.js") + ); + + using (var solution = project.Generate().ToVs()) { + solution.ExecuteCommand("Debug.Start"); + solution.WaitForMode(dbgDebugMode.dbgRunMode); + + for (int i = 0; i < 10 && !File.Exists(filename); i++) { + System.Threading.Thread.Sleep(1000); + } + Assert.IsTrue(File.Exists(filename), "debugger port not written out"); + solution.ExecuteCommand("Debug.StopDebugging"); + + Assert.AreEqual( + File.ReadAllText(filename), + "1234" + ); + } + } + + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestEnvironmentVariables() { + var filename = Path.Combine(TestData.GetTempPath(), Path.GetRandomFileName()); + Console.WriteLine("Temp file is: {0}", filename); + var code = String.Format(@" +require('fs').writeFileSync('{0}', process.env.fob + process.env.bar + process.env.baz); +while(true) {{ +}}", filename.Replace("\\", "\\\\")); + + var project = Project("EnvironmentVariables", + Compile("server", code), + Property(NodejsConstants.Environment, "fob=1\nbar=2;3\r\nbaz=4"), + Property(CommonConstants.StartupFile, "server.js") + ); + + using (var solution = project.Generate().ToVs()) { + solution.ExecuteCommand("Debug.Start"); + solution.WaitForMode(dbgDebugMode.dbgRunMode); + + for (int i = 0; i < 10 && !File.Exists(filename); i++) { + System.Threading.Thread.Sleep(1000); + } + Assert.IsTrue(File.Exists(filename), "environment variables not written out"); + solution.ExecuteCommand("Debug.StopDebugging"); + + Assert.AreEqual( + File.ReadAllText(filename), + "12;34" + ); + } + } + + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestEnvironmentVariablesNoDebugging() { + var filename = Path.Combine(TestData.GetTempPath(), Path.GetRandomFileName()); + Console.WriteLine("Temp file is: {0}", filename); + var code = String.Format(@" +require('fs').writeFileSync('{0}', process.env.fob + process.env.bar + process.env.baz); +", filename.Replace("\\", "\\\\")); + + var project = Project("EnvironmentVariables", + Compile("server", code), + Property(NodejsConstants.Environment, "fob=1\nbar=2;3\r\nbaz=4"), + Property(CommonConstants.StartupFile, "server.js") + ); + + using (var solution = project.Generate().ToVs()) { + solution.ExecuteCommand("Debug.StartWithoutDebugging"); + + for (int i = 0; i < 10 && !File.Exists(filename); i++) { + System.Threading.Thread.Sleep(1000); + } + Assert.IsTrue(File.Exists(filename), "environment variables not written out"); + + Assert.AreEqual( + File.ReadAllText(filename), + "12;34" + ); + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestProjectProperties() { + var filename = Path.Combine(TestData.GetTempPath(), Path.GetRandomFileName()); + + var project = Project("ProjectProperties", + Compile("server"), + Property(NodejsConstants.Environment, "fob=1\r\nbar=2;3\nbaz=4"), + Property(NodejsConstants.DebuggerPort, "1234"), + Property(CommonConstants.StartupFile, "server.js") + ); + + using (var solution = project.Generate().ToVs()) { + var projectNode = solution.WaitForItem("ProjectProperties"); + AutomationWrapper.Select(projectNode); + + solution.ExecuteCommand("ClassViewContextMenus.ClassViewMultiselectProjectReferencesItems.Properties"); + AutomationElement doc = null; + for (int i = 0; i < 10; i++) { + doc = ((VisualStudioInstance)solution).App.GetDocumentTab("ProjectProperties"); + if (doc != null) { + break; + } + System.Threading.Thread.Sleep(1000); + } + Assert.IsNotNull(doc, "Failed to find project properties tab"); + + var debuggerPort = + new TextBox( + new AutomationWrapper(doc).FindByAutomationId("_debuggerPort") + ); + var envVars = new TextBox( + new AutomationWrapper(doc).FindByAutomationId("_envVars") + ); + + Assert.AreEqual(debuggerPort.Value, "1234"); + Assert.AreEqual(envVars.Value, "fob=1\r\nbar=2;3\r\nbaz=4"); + + debuggerPort.Value = "2468"; + + // Multi-line text box does not support setting value via automation. + envVars.SetFocus(); + Keyboard.ControlA(); + Keyboard.Backspace(); + Keyboard.Type("fob=0\nbar=0;0\nbaz=0"); + + solution.ExecuteCommand("File.SaveAll"); + + var projFile = File.ReadAllText(solution.GetProject("ProjectProperties").FullName); + Assert.AreNotEqual(-1, projFile.IndexOf("2468")); + Assert.AreNotEqual(-1, projFile.IndexOf("fob=0\r\nbar=0;0\r\nbaz=0")); + } + } + + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestClientServerIntelliSenseModes() { + string + solutionLabel = "Solution 'ClientServerCode' (1 project)", + projectLabel = "ClientServerCode", + nodeDirectoryLabel = NodejsFolderNode.AppendLabel("NodeDirectory", FolderContentType.Node), + nodeSubDirectoryLabel = NodejsFolderNode.AppendLabel("NodeSubDirectory", FolderContentType.Node), + browserDirectoryLabel = NodejsFolderNode.AppendLabel("BrowserDirectory", FolderContentType.Browser), + emptyBrowserSubDirectoryLabel = "BrowserSubDirectory", + browserSubDirectoryLabel = NodejsFolderNode.AppendLabel("BrowserSubDirectory", FolderContentType.Browser), + mixedDirectoryLabel = NodejsFolderNode.AppendLabel("MixedDirectory", FolderContentType.Mixed), + mixedDirectoryBrowserDirectoryLabel = NodejsFolderNode.AppendLabel("BrowserDirectory", FolderContentType.Browser), + mixedDirectoryNodeDirectoryLabel = NodejsFolderNode.AppendLabel("NodeDirectory", FolderContentType.Node), + browserCodeLabel = "browserCode.js", + mixedDirectoryRenamedLabel = NodejsFolderNode.AppendLabel("MixedDirectoryRenamed", FolderContentType.Mixed); + + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\ClientServerCode\ClientServerCode.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", true)) { + // Wait until project is loaded + var solutionExplorer = app.OpenSolutionExplorer(); + + solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + "app.js"); + + var nodejsProject = app.GetProject("ClientServerCode").GetNodejsProject(); + + var projectNode = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel); + + var browserDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + browserDirectoryLabel + ); + Assert.IsNotNull( + browserDirectory, + "Browser directories should be labeled as such. Could not find " + browserDirectoryLabel); + + var browserSubDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + browserDirectoryLabel, + emptyBrowserSubDirectoryLabel + ); + Assert.IsNotNull( + browserSubDirectory, + "Project initialization: could not find " + emptyBrowserSubDirectoryLabel); + + var nodeDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + nodeDirectoryLabel + ); + Assert.IsNotNull( + nodeDirectory, + "Node directories should be labeled as such. Could not find " + nodeDirectoryLabel); + + var nodeSubDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + nodeDirectoryLabel, + nodeSubDirectoryLabel + ); + Assert.IsNotNull( + nodeSubDirectory, + "Project initialization: could not find " + nodeSubDirectoryLabel); + + projectNode.Select(); + using (var newItem = NewItemDialog.FromDte(app)) { + newItem.FileName = "newItem.js"; + newItem.OK(); + } + + Assert.AreEqual( + "Compile", + nodejsProject.GetItemType("newItem.js"), + "Top level files should be set to item type 'Compile'"); + + Keyboard.Type("process."); + Keyboard.Type(Keyboard.CtrlSpace.ToString()); + + using (var session = app.GetDocument(Path.Combine(nodejsProject.ProjectHome, @"newItem.js")).WaitForSession()) { + var completions = session.Session.CompletionSets.First().Completions.Select(x => x.InsertionText); + Assert.IsTrue( + completions.Contains("env"), + "New documents of the node type should open with default VS editor" + ); + } + + browserSubDirectory.Select(); + using (var newBrowserItem = NewItemDialog.FromDte(app)) { + newBrowserItem.FileName = "newBrowserItem.js"; + newBrowserItem.OK(); + } + + Keyboard.Type("document."); + System.Threading.Thread.Sleep(2000); + Keyboard.Type(Keyboard.CtrlSpace.ToString()); + + using (var session = app.GetDocument(Path.Combine(nodejsProject.ProjectHome, @"BrowserDirectory\browserSubDirectory\newBrowserItem.js")).WaitForSession()) { + var completions = session.Session.CompletionSets.First().Completions.Select(x => x.InsertionText); + Assert.IsTrue( + completions.Contains("body"), + "New documents of the browser type should open with default VS editor" + ); + } + + browserSubDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + browserDirectoryLabel, + browserSubDirectoryLabel + ); + Assert.IsNotNull( + browserSubDirectory, + "Folder label was not updated to " + browserSubDirectoryLabel); + + var newBrowserItemFile = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + browserDirectoryLabel, + browserSubDirectoryLabel, + "newBrowserItem.js" + ); + Assert.AreEqual( + "Content", + nodejsProject.GetItemType(@"BrowserDirectory\BrowserSubDirectory\newBrowserItem.js"), + "Adding a javascript file to a 'browser' directory should set the item type as Content."); + + var mixedDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + mixedDirectoryLabel + ); + Assert.IsNotNull( + mixedDirectory, + "Folder with mixed browser/node content should be specified as such. Could not find: " + mixedDirectoryLabel); + + nodeDirectory.Select(); + using (var newTypeScriptItem = NewItemDialog.FromDte(app)) { + newTypeScriptItem.FileName = "newTypeScriptItem.ts"; + newTypeScriptItem.OK(); + } + + Assert.AreEqual( + "TypeScriptCompile", + nodejsProject.GetItemType(@"NodeDirectory\newTypeScriptItem.ts"), + "Non-javascript files should retain their content type."); + + var newBrowserItemNode = nodejsProject.FindNodeByFullPath( + Path.Combine(nodejsProject.ProjectHome, @"BrowserDirectory\BrowserSubDirectory\newBrowserItem.js") + ); + + newBrowserItemNode.ExcludeFromProject(); + var excludedBrowserItem = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + browserDirectoryLabel, + emptyBrowserSubDirectoryLabel + ); + Assert.IsNotNull( + emptyBrowserSubDirectoryLabel, + "Label should be removed when there are no included javascript files the directory. Could not find " + emptyBrowserSubDirectoryLabel); + + (newBrowserItemNode as NodejsFileNode).IncludeInProject(false); + var includedBrowserItem = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + browserDirectoryLabel, + browserSubDirectoryLabel + ); + Assert.IsNotNull( + includedBrowserItem, + "Label should be added when a javascript file is included in the directory. Could not find " + browserSubDirectoryLabel); + + var mixedDirectoryNode = app.GetProject("ClientServerCode").GetNodejsProject().FindNodeByFullPath( + Path.Combine(nodejsProject.ProjectHome, @"MixedDirectory\")); + + System.Threading.Thread.Sleep(2000); + mixedDirectory.Select(); + Keyboard.PressAndRelease(Key.F2); + Keyboard.Type("MixedDirectoryRenamed"); + Keyboard.PressAndRelease(Key.Enter); + + mixedDirectoryNode.ExpandItem(EXPANDFLAGS.EXPF_ExpandFolderRecursively); + var renamedMixedDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + mixedDirectoryRenamedLabel, + mixedDirectoryBrowserDirectoryLabel, + browserCodeLabel + ); + + Assert.IsNotNull( + renamedMixedDirectory, + "Renaming mixed directory failed: could not find " + browserCodeLabel); + + newBrowserItemNode.ItemNode.ItemTypeName = "Compile"; + + var nodeBrowserSubDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + NodejsFolderNode.AppendLabel("BrowserDirectory", FolderContentType.Mixed), + NodejsFolderNode.AppendLabel("BrowserSubDirectory", FolderContentType.Node) + ); + Assert.IsNotNull( + nodeBrowserSubDirectory, + "Changing the item type should change the directory label. Could not find " + + NodejsFolderNode.AppendLabel("BrowserSubDirectory", FolderContentType.Node) + ); + + var nodeDirectoryNode = app.GetProject("ClientServerCode").GetNodejsProject().FindNodeByFullPath( + Path.Combine(app.GetProject("ClientServerCode").GetNodejsProject().ProjectHome, + @"NodeDirectory\")); + (nodeDirectoryNode as NodejsFolderNode).SetItemTypeRecursively(VSLangProj.prjBuildAction.prjBuildActionContent); + + Assert.AreEqual( + "Content", + nodejsProject.GetItemType(@"NodeDirectory\NodeSubDirectory\nodeCode.js"), + "nodeCode.js file should be marked as content after recursively setting directory contents as Content." + ); + Assert.AreEqual( + "TypeScriptCompile", + nodejsProject.GetItemType(@"NodeDirectory\newTypeScriptItem.ts"), + "Only javascript file item types should change when marking item types recursively." + ); + + var fromPoint = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + mixedDirectoryRenamedLabel, + mixedDirectoryNodeDirectoryLabel + ).GetClickablePoint(); + + var toPoint = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + mixedDirectoryRenamedLabel, + mixedDirectoryBrowserDirectoryLabel + ).GetClickablePoint(); + + Mouse.MoveTo(fromPoint); + Mouse.Down(MouseButton.Left); + + Mouse.MoveTo(toPoint); + Mouse.Up(MouseButton.Left); + + var draggedDirectory = solutionExplorer.WaitForItem( + solutionLabel, + projectLabel, + mixedDirectoryRenamedLabel, + NodejsFolderNode.AppendLabel("BrowserDirectory", FolderContentType.Mixed), + NodejsFolderNode.AppendLabel("NodeDirectory", FolderContentType.Node) + ); + Assert.IsNotNull( + draggedDirectory, + "Labels not properly updated after dragging and dropping directory." + ); + } + } + } + } +} diff --git a/Nodejs/Tests/Core.UI/SmartIndent.cs b/Nodejs/Tests/Core.UI/SmartIndent.cs index bc7c20a0a..572c57682 100644 --- a/Nodejs/Tests/Core.UI/SmartIndent.cs +++ b/Nodejs/Tests/Core.UI/SmartIndent.cs @@ -1,369 +1,369 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Windows.Forms; -using EnvDTE; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.VisualStudioTools.VSTestHost; -using TestUtilities; -using TestUtilities.SharedProject; -using TestUtilities.UI; - -namespace Microsoft.Nodejs.Tests.UI { - [TestClass] - public class SmartIndent : NodejsProjectTest { - public static ProjectDefinition BasicProject = new ProjectDefinition( - "AutoIndent", - NodejsProject, - Compile("server", ""), - Compile("Bug384", @"Foo.prototype.Bar = function (callback) { - - var result = null; - for (var i = 0; i < 10; i++) { - if (i == 5) { - console.log(i); - break; - } - } - -}"), - Compile("Bug1198", @"if (true) - if (true) - if (true) - return; -") - ); - - [ClassInitialize] - public static void DoDeployment(TestContext context) { - AssertListener.Initialize(); - } - - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void SmartIndentBug1198() { - using (new DefaultSmartIndentOptionHolder()) { - using (var solution = BasicProject.Generate().ToVs()) { - var doc = solution.OpenItem("AutoIndent", "Bug1198.js"); - doc.Invoke(() => doc.TextView.Caret.MoveTo(doc.TextView.TextViewLines[4])); - Keyboard.Type(System.Windows.Input.Key.End); - Keyboard.Type(" "); - Assert.AreEqual( - 9, - doc.TextView.Caret.Position.BufferPosition.GetContainingLine().Length - ); - } - } - } - - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void SmartIndentBug384() { - using (new DefaultSmartIndentOptionHolder()) { - using (var solution = BasicProject.Generate().ToVs()) { - var doc = solution.OpenItem("AutoIndent", "Bug384.js"); - doc.Invoke(() => doc.TextView.Caret.MoveTo(doc.TextView.TextViewLines[9])); - Keyboard.Type(System.Windows.Input.Key.End); - Keyboard.Type(" "); - Assert.AreEqual( - 5, - doc.TextView.Caret.Position.BufferPosition.GetContainingLine().Length - ); - } - } - - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void SmartIndentBug3() { - using (new DefaultSmartIndentOptionHolder(insertTabs: true)) { - var testCases = new[] { - new { - Typed = @"var promise = new Promise(function () { -function a() { -if (b) -b = c; -100".Replace("\r\n", "\r"), - Expected =@"var promise = new Promise(function () { -\tfunction a() { -\t\tif (b) -\t\t\tb = c; -\t\t100 -\t} -})".Replace(@"\t", "\t") } - }; - - VerifyAutoIndentTestCases(testCases); - } - } - - private class AutoIndentTestCase { - public string Typed { get; set; } - public string Expected { get; set; } - } - - private void VerifyAutoIndentTestCases(dynamic[] testCases) - { - using (var solution = BasicProject.Generate().ToVs()) { - foreach (var testCase in testCases) { - Console.WriteLine("Typing : {0}", testCase.Typed); - Console.WriteLine("Expected: {0}", testCase.Expected); - AutoIndentTest( - solution, - testCase.Typed, - testCase.Expected - ); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void SmartIndentBasic() { - using (new DefaultSmartIndentOptionHolder(braceCompletion:false)) { - var testCases = new[] { - // https://nodejstools.codeplex.com/workitem/1176 - new { - Typed = "if (true)\rwhile(true)\rfalse;\r100", - Expected = @"if (true) - while (true) - false; -100" - }, - new { - Typed = "if (true)\rif (true)\rfalse;\relse", - Expected = @"if (true) - if (true) - false; - else" - }, - // no { - new { - Typed = "if (true)\r42\relse \r100\r200", - Expected = @"if (true) - 42 -else - 100 -200" - }, - new { - Typed = "if (true)\r42\relse if (true)\r100\r200", - Expected = @"if (true) - 42 -else if (true) - 100 -200" - }, - new { - Typed = "for(var i = 0; i<100; i++)\r42\r100", - Expected = @"for(var i = 0; i<100; i++) - 42 -100" - }, - new { - Typed = "while(true)\r42\r100", - Expected = @"while (true) - 42 -100" - }, - new { - Typed = "do \r42\rwhile(false)\r100", - Expected = @"do - 42 -while(false) -100" - }, - // grouping - new { - Typed = "x = [1,\r2,\r3\r]", - Expected = @"x = [1, - 2, - 3 - ]" - }, - // nested function, dedent keyword - new { - Typed = "function a() {\rfunction b() {\rreturn\r}\r\b}", - Expected = @"function a() { - function b() { - return - } -}" - }, - // nested function, dedent keyword - new { - Typed = "function a() {\rfunction b() {\rfoo \r\b}\r\b}", - Expected = @"function a() { - function b() { - foo - } -}" - }, - // basic indentation - new { - Typed = "if (true) {\rconsole.log('hi');\r\b}", - Expected = @"if (true) { - console.log('hi'); -}" - }, - // enter in multiline string - new { - Typed = "if (true) {\r\"foo\\\rbar\"\r\b}", - Expected = @"if (true) { - ""foo\ -bar"" -}" - }, - // enter in multiline comment - new { - Typed = "if (true) {\r/*foo\rbar*/\r\b}", - Expected = @"if (true) { - /*foo - * bar*/ -}" - }, - // auto dedent after return - new { - Typed = "if (true) {\rreturn \r}", - Expected = @"if (true) { - return -}" - }, - // auto dedent after return; - new { - Typed = "if (true) {\rreturn; \r}", - Expected = @"if (true) { - return; -}" - }, - // auto dedent after return; - new { - Typed = "if (true) {\rreturn;; \r}", - Expected = @"if (true) { - return;; -}" - }, - // auto dedent normal statement - new { - Typed = "if (true) {\rf(x)\r\b}", - Expected = @"if (true) { - f(x) -}" - }, - // auto dedent function call w/o params - new { - Typed = "if (true) {\rfoo()\r\b}", - Expected = @"if (true) { - foo() -}" - }, - // auto dedent normal statement ending in semicolon - new { - Typed = "if (true) {\rf(x);\r\b}", - Expected = @"if (true) { - f(x); -}" - }, - }; - - VerifyAutoIndentTestCases(testCases); - } - } - - - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void BraceCompletion_BasicTests() { - using (new DefaultSmartIndentOptionHolder()) { - var testCases = new[] { - - // auto indent after return during brace completion - // https://nodejstools.codeplex.com/workitem/1466 - new { - Typed = "function myfunc(name){\rm();", - Expected = "function myfunc(name){\r\n m();\r\n}" - }, - // https://nodejstools.codeplex.com/workitem/1560 - new { - Typed = "var a = function (test) {\rreturn {\rtest", - Expected = "var a = function (test) {\r\n return {\r\n test\r\n }\r\n}" - }, - new { - Typed = "var a = function (test) {\rreturn [\rtest", - Expected = "var a = function (test) {\r\n return [\r\n test\r\n ]\r\n}" - }, - new { - Typed = "var a = function (test) {\rreturn (\rtest", - Expected = "var a = function (test) {\r\n return (\r\n test\r\n )\r\n}" - }, - }; - - VerifyAutoIndentTestCases(testCases); - } - } - - private static void AutoIndentTest(IVisualStudioInstance solution, string typedText, string expectedText) { - var doc = solution.OpenItem("AutoIndent", "server.js"); - doc.MoveCaret(1, 1); - doc.Invoke(() => doc.TextView.Caret.EnsureVisible()); - doc.SetFocus(); - - Keyboard.Type(typedText); - - string actual = null; - for (int i = 0; i < 100; i++) { - actual = doc.TextView.TextBuffer.CurrentSnapshot.GetText(); - - if (expectedText == actual) { - break; - } - System.Threading.Thread.Sleep(100); - } - Assert.AreEqual(expectedText, actual); - - VSTestContext.DTE.ActiveWindow.Close(vsSaveChanges.vsSaveChangesNo); - } - - - private class DefaultSmartIndentOptionHolder : IDisposable { - List _options = new List(); - public DefaultSmartIndentOptionHolder( - bool braceCompletion = true, - bool insertTabs = false, - int indentSize = 4, - int tabSize = 4) { - _options.AddRange(new[] { -#if DEV12_OR_LATER - new OptionHolder("TextEditor", "Node.js", "BraceCompletion", braceCompletion), -#endif - new OptionHolder("TextEditor", "Node.js", "InsertTabs", insertTabs), - new OptionHolder("TextEditor", "Node.js", "IndentSize", indentSize), - new OptionHolder("TextEditor", "Node.js", "TabSize", tabSize) - }); - } - - public void Dispose() { - _options.ForEach(option => option.Dispose()); - } - } - } - -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Windows.Forms; +using EnvDTE; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Microsoft.VisualStudioTools.VSTestHost; +using TestUtilities; +using TestUtilities.SharedProject; +using TestUtilities.UI; + +namespace Microsoft.Nodejs.Tests.UI { + [TestClass] + public class SmartIndent : NodejsProjectTest { + public static ProjectDefinition BasicProject = new ProjectDefinition( + "AutoIndent", + NodejsProject, + Compile("server", ""), + Compile("Bug384", @"Foo.prototype.Bar = function (callback) { + + var result = null; + for (var i = 0; i < 10; i++) { + if (i == 5) { + console.log(i); + break; + } + } + +}"), + Compile("Bug1198", @"if (true) + if (true) + if (true) + return; +") + ); + + [ClassInitialize] + public static void DoDeployment(TestContext context) { + AssertListener.Initialize(); + } + + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void SmartIndentBug1198() { + using (new DefaultSmartIndentOptionHolder()) { + using (var solution = BasicProject.Generate().ToVs()) { + var doc = solution.OpenItem("AutoIndent", "Bug1198.js"); + doc.Invoke(() => doc.TextView.Caret.MoveTo(doc.TextView.TextViewLines[4])); + Keyboard.Type(System.Windows.Input.Key.End); + Keyboard.Type(" "); + Assert.AreEqual( + 9, + doc.TextView.Caret.Position.BufferPosition.GetContainingLine().Length + ); + } + } + } + + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void SmartIndentBug384() { + using (new DefaultSmartIndentOptionHolder()) { + using (var solution = BasicProject.Generate().ToVs()) { + var doc = solution.OpenItem("AutoIndent", "Bug384.js"); + doc.Invoke(() => doc.TextView.Caret.MoveTo(doc.TextView.TextViewLines[9])); + Keyboard.Type(System.Windows.Input.Key.End); + Keyboard.Type(" "); + Assert.AreEqual( + 5, + doc.TextView.Caret.Position.BufferPosition.GetContainingLine().Length + ); + } + } + + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void SmartIndentBug3() { + using (new DefaultSmartIndentOptionHolder(insertTabs: true)) { + var testCases = new[] { + new { + Typed = @"var promise = new Promise(function () { +function a() { +if (b) +b = c; +100".Replace("\r\n", "\r"), + Expected =@"var promise = new Promise(function () { +\tfunction a() { +\t\tif (b) +\t\t\tb = c; +\t\t100 +\t} +})".Replace(@"\t", "\t") } + }; + + VerifyAutoIndentTestCases(testCases); + } + } + + private class AutoIndentTestCase { + public string Typed { get; set; } + public string Expected { get; set; } + } + + private void VerifyAutoIndentTestCases(dynamic[] testCases) + { + using (var solution = BasicProject.Generate().ToVs()) { + foreach (var testCase in testCases) { + Console.WriteLine("Typing : {0}", testCase.Typed); + Console.WriteLine("Expected: {0}", testCase.Expected); + AutoIndentTest( + solution, + testCase.Typed, + testCase.Expected + ); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void SmartIndentBasic() { + using (new DefaultSmartIndentOptionHolder(braceCompletion:false)) { + var testCases = new[] { + // https://nodejstools.codeplex.com/workitem/1176 + new { + Typed = "if (true)\rwhile(true)\rfalse;\r100", + Expected = @"if (true) + while (true) + false; +100" + }, + new { + Typed = "if (true)\rif (true)\rfalse;\relse", + Expected = @"if (true) + if (true) + false; + else" + }, + // no { + new { + Typed = "if (true)\r42\relse \r100\r200", + Expected = @"if (true) + 42 +else + 100 +200" + }, + new { + Typed = "if (true)\r42\relse if (true)\r100\r200", + Expected = @"if (true) + 42 +else if (true) + 100 +200" + }, + new { + Typed = "for(var i = 0; i<100; i++)\r42\r100", + Expected = @"for(var i = 0; i<100; i++) + 42 +100" + }, + new { + Typed = "while(true)\r42\r100", + Expected = @"while (true) + 42 +100" + }, + new { + Typed = "do \r42\rwhile(false)\r100", + Expected = @"do + 42 +while(false) +100" + }, + // grouping + new { + Typed = "x = [1,\r2,\r3\r]", + Expected = @"x = [1, + 2, + 3 + ]" + }, + // nested function, dedent keyword + new { + Typed = "function a() {\rfunction b() {\rreturn\r}\r\b}", + Expected = @"function a() { + function b() { + return + } +}" + }, + // nested function, dedent keyword + new { + Typed = "function a() {\rfunction b() {\rfoo \r\b}\r\b}", + Expected = @"function a() { + function b() { + foo + } +}" + }, + // basic indentation + new { + Typed = "if (true) {\rconsole.log('hi');\r\b}", + Expected = @"if (true) { + console.log('hi'); +}" + }, + // enter in multiline string + new { + Typed = "if (true) {\r\"foo\\\rbar\"\r\b}", + Expected = @"if (true) { + ""foo\ +bar"" +}" + }, + // enter in multiline comment + new { + Typed = "if (true) {\r/*foo\rbar*/\r\b}", + Expected = @"if (true) { + /*foo + * bar*/ +}" + }, + // auto dedent after return + new { + Typed = "if (true) {\rreturn \r}", + Expected = @"if (true) { + return +}" + }, + // auto dedent after return; + new { + Typed = "if (true) {\rreturn; \r}", + Expected = @"if (true) { + return; +}" + }, + // auto dedent after return; + new { + Typed = "if (true) {\rreturn;; \r}", + Expected = @"if (true) { + return;; +}" + }, + // auto dedent normal statement + new { + Typed = "if (true) {\rf(x)\r\b}", + Expected = @"if (true) { + f(x) +}" + }, + // auto dedent function call w/o params + new { + Typed = "if (true) {\rfoo()\r\b}", + Expected = @"if (true) { + foo() +}" + }, + // auto dedent normal statement ending in semicolon + new { + Typed = "if (true) {\rf(x);\r\b}", + Expected = @"if (true) { + f(x); +}" + }, + }; + + VerifyAutoIndentTestCases(testCases); + } + } + + + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void BraceCompletion_BasicTests() { + using (new DefaultSmartIndentOptionHolder()) { + var testCases = new[] { + + // auto indent after return during brace completion + // https://nodejstools.codeplex.com/workitem/1466 + new { + Typed = "function myfunc(name){\rm();", + Expected = "function myfunc(name){\r\n m();\r\n}" + }, + // https://nodejstools.codeplex.com/workitem/1560 + new { + Typed = "var a = function (test) {\rreturn {\rtest", + Expected = "var a = function (test) {\r\n return {\r\n test\r\n }\r\n}" + }, + new { + Typed = "var a = function (test) {\rreturn [\rtest", + Expected = "var a = function (test) {\r\n return [\r\n test\r\n ]\r\n}" + }, + new { + Typed = "var a = function (test) {\rreturn (\rtest", + Expected = "var a = function (test) {\r\n return (\r\n test\r\n )\r\n}" + }, + }; + + VerifyAutoIndentTestCases(testCases); + } + } + + private static void AutoIndentTest(IVisualStudioInstance solution, string typedText, string expectedText) { + var doc = solution.OpenItem("AutoIndent", "server.js"); + doc.MoveCaret(1, 1); + doc.Invoke(() => doc.TextView.Caret.EnsureVisible()); + doc.SetFocus(); + + Keyboard.Type(typedText); + + string actual = null; + for (int i = 0; i < 100; i++) { + actual = doc.TextView.TextBuffer.CurrentSnapshot.GetText(); + + if (expectedText == actual) { + break; + } + System.Threading.Thread.Sleep(100); + } + Assert.AreEqual(expectedText, actual); + + VSTestContext.DTE.ActiveWindow.Close(vsSaveChanges.vsSaveChangesNo); + } + + + private class DefaultSmartIndentOptionHolder : IDisposable { + List _options = new List(); + public DefaultSmartIndentOptionHolder( + bool braceCompletion = true, + bool insertTabs = false, + int indentSize = 4, + int tabSize = 4) { + _options.AddRange(new[] { +#if DEV12_OR_LATER + new OptionHolder("TextEditor", "Node.js", "BraceCompletion", braceCompletion), +#endif + new OptionHolder("TextEditor", "Node.js", "InsertTabs", insertTabs), + new OptionHolder("TextEditor", "Node.js", "IndentSize", indentSize), + new OptionHolder("TextEditor", "Node.js", "TabSize", tabSize) + }); + } + + public void Dispose() { + _options.ForEach(option => option.Dispose()); + } + } + } + +} diff --git a/Nodejs/Tests/Core.UI/SnippetsTests.cs b/Nodejs/Tests/Core.UI/SnippetsTests.cs index 1522f11f2..814b6419e 100644 --- a/Nodejs/Tests/Core.UI/SnippetsTests.cs +++ b/Nodejs/Tests/Core.UI/SnippetsTests.cs @@ -1,234 +1,234 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using EnvDTE; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TestUtilities; -using TestUtilities.SharedProject; -using TestUtilities.UI; - -namespace Microsoft.Nodejs.Tests.UI { - [TestClass] - public class SnippetsTests:NodejsProjectTest { - - private static ProjectDefinition BasicProject = Project( - "SnippetsTest", - Compile("server", ""), - Compile("multiline", "one\r\ntwo\r\nthree"), - Compile("nonempty", "nonempty"), - Compile("indented", "if (true) {\r\n \r\n}") - ); - - class Snippet { - public readonly string Shortcut; - public readonly string Expected; - public readonly Declaration[] Declarations; - - public Snippet(string shortcut, string expected, params Declaration[] declarations) { - Shortcut = shortcut; - Expected = expected; - Declarations = declarations; - } - } - - class Declaration { - public readonly string Replacement; - public readonly string Expected; - - public Declaration(string replacement, string expected) { - Replacement = replacement; - Expected = expected; - } - } - - private static readonly Snippet[] BasicSnippets = new Snippet[] { - new Snippet( - "while", - "while (true) {\r\n $body$\r\n};\r\n", - new Declaration("false", "while (false) {\r\n $body$\r\n};\r\n") - ), - new Snippet( - "if", - "if (true) {\r\n $body$\r\n}\r\n", - new Declaration("false", "if (false) {\r\n $body$\r\n}\r\n") - ), - new Snippet( - "iife", - "(function (undefined) {\r\n $body$\r\n})();\r\n", - new Declaration("name","(function (name) {\r\n $body$\r\n})();\r\n") - ), - new Snippet( - "for", - "for (var i = 0; i < length; i++) {\r\n $body$\r\n};\r\n", - new Declaration("counter", "for (var counter = 0; counter < length; counter++) {\r\n $body$\r\n};\r\n") - ) - }; - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestInsertSnippet() { - using (var solution = BasicProject.Generate().ToVs()) { - - foreach (var snippet in BasicSnippets) { - TestOneInsertSnippet(solution, snippet, "Nodejs"); - - solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestSelectedIndented() { - using (var solution = BasicProject.Generate().ToVs()) { - var server = solution.OpenItem("SnippetsTest", "indented.js"); - server.MoveCaret(2, 5); - server.Invoke(() => server.TextView.Caret.EnsureVisible()); - server.SetFocus(); - - Keyboard.Type("if\t"); - server.WaitForText("if (true) {\r\n if (true) {\r\n \r\n }\r\n \r\n}"); - Keyboard.Type("\r"); - Keyboard.Type("testing"); - server.WaitForText("if (true) {\r\n if (true) {\r\n testing\r\n }\r\n \r\n}"); - - solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestSurroundWithSnippet() { - using (var solution = BasicProject.Generate().ToVs()) { - foreach (var snippet in BasicSnippets) { - TestOneSurroundWithSnippet(solution, snippet, "Nodejs"); - - solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestSelected() { - var snippet = new Snippet( - "if", - "if (true) {\r\n $body$\r\n}\r\n", - new Declaration("false", "if (false) {\r\n $body$\r\n}\r\n") - ); - using (var solution = BasicProject.Generate().ToVs()) { - var app = TestOneTabSnippet(solution, snippet); - - Keyboard.Type("testing"); - app.WaitForText("if (false) {\r\n testing\r\n}\r\n"); - - solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); - } - - } - - private static IEditor TestOneSurroundWithSnippet(IVisualStudioInstance solution, Snippet snippet, string category, string body = "nonempty", string file = "nonempty.js") { - Console.WriteLine("Testing: {0}", snippet.Shortcut); - var server = solution.OpenItem("SnippetsTest", file); - server.Select(1, 1, server.Text.Length); - server.Invoke(() => server.TextView.Caret.EnsureVisible()); - server.SetFocus(); - - solution.ExecuteCommand("Edit.SurroundWith"); - Keyboard.Type(category + "\t"); - - return VerifySnippet(snippet, body, server); - } - - private static IEditor TestOneInsertSnippet(IVisualStudioInstance solution, Snippet snippet, string category, string body = "nonempty", string file ="nonempty.js") { - Console.WriteLine("Testing: {0}", snippet.Shortcut); - var server = solution.OpenItem("SnippetsTest", file); - server.Select(1, 1, server.Text.Length); - server.Invoke(() => server.TextView.Caret.EnsureVisible()); - server.SetFocus(); - - solution.ExecuteCommand("Edit.InsertSnippet"); - Keyboard.Type(category + "\t"); - - return VerifySnippet(snippet, body, server); - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestBasicSnippetsTab() { - using (var solution = BasicProject.Generate().ToVs()) { - foreach (var snippet in BasicSnippets) { - TestOneTabSnippet(solution, snippet); - - solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); - } - } - } - - private static IEditor TestOneTabSnippet(IVisualStudioInstance solution, Snippet snippet) { - Console.WriteLine("Testing: {0}", snippet.Shortcut); - var server = solution.OpenItem("SnippetsTest", "server.js"); - server.MoveCaret(1, 1); - server.Invoke(() => server.TextView.Caret.EnsureVisible()); - server.SetFocus(); - - return VerifySnippet(snippet, "", server); - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TestSurroundWithMultiline() { - using (var solution = BasicProject.Generate().ToVs()) { - foreach (var snippet in BasicSnippets) { - TestOneSurroundWithSnippet( - solution, - snippet, - "Nodejs", - "one\r\n two\r\n three", - "multiline.js" - ); - - solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); - } - } - } - - private static IEditor VerifySnippet(Snippet snippet, string body, IEditor server) { - Keyboard.Type(snippet.Shortcut + "\t"); - - server.WaitForText(snippet.Expected.Replace("$body$", body)); - - foreach (var decl in snippet.Declarations) { - Console.WriteLine("Declaration: {0}", decl.Replacement); - Keyboard.Type(decl.Replacement); - Keyboard.Type("\t"); - server.WaitForText(decl.Expected.Replace("$body$", body)); - Keyboard.Type("\t"); - } - Keyboard.Type("\r"); - return server; - } - - } - -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using EnvDTE; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; +using TestUtilities.SharedProject; +using TestUtilities.UI; + +namespace Microsoft.Nodejs.Tests.UI { + [TestClass] + public class SnippetsTests:NodejsProjectTest { + + private static ProjectDefinition BasicProject = Project( + "SnippetsTest", + Compile("server", ""), + Compile("multiline", "one\r\ntwo\r\nthree"), + Compile("nonempty", "nonempty"), + Compile("indented", "if (true) {\r\n \r\n}") + ); + + class Snippet { + public readonly string Shortcut; + public readonly string Expected; + public readonly Declaration[] Declarations; + + public Snippet(string shortcut, string expected, params Declaration[] declarations) { + Shortcut = shortcut; + Expected = expected; + Declarations = declarations; + } + } + + class Declaration { + public readonly string Replacement; + public readonly string Expected; + + public Declaration(string replacement, string expected) { + Replacement = replacement; + Expected = expected; + } + } + + private static readonly Snippet[] BasicSnippets = new Snippet[] { + new Snippet( + "while", + "while (true) {\r\n $body$\r\n};\r\n", + new Declaration("false", "while (false) {\r\n $body$\r\n};\r\n") + ), + new Snippet( + "if", + "if (true) {\r\n $body$\r\n}\r\n", + new Declaration("false", "if (false) {\r\n $body$\r\n}\r\n") + ), + new Snippet( + "iife", + "(function (undefined) {\r\n $body$\r\n})();\r\n", + new Declaration("name","(function (name) {\r\n $body$\r\n})();\r\n") + ), + new Snippet( + "for", + "for (var i = 0; i < length; i++) {\r\n $body$\r\n};\r\n", + new Declaration("counter", "for (var counter = 0; counter < length; counter++) {\r\n $body$\r\n};\r\n") + ) + }; + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestInsertSnippet() { + using (var solution = BasicProject.Generate().ToVs()) { + + foreach (var snippet in BasicSnippets) { + TestOneInsertSnippet(solution, snippet, "Nodejs"); + + solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestSelectedIndented() { + using (var solution = BasicProject.Generate().ToVs()) { + var server = solution.OpenItem("SnippetsTest", "indented.js"); + server.MoveCaret(2, 5); + server.Invoke(() => server.TextView.Caret.EnsureVisible()); + server.SetFocus(); + + Keyboard.Type("if\t"); + server.WaitForText("if (true) {\r\n if (true) {\r\n \r\n }\r\n \r\n}"); + Keyboard.Type("\r"); + Keyboard.Type("testing"); + server.WaitForText("if (true) {\r\n if (true) {\r\n testing\r\n }\r\n \r\n}"); + + solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestSurroundWithSnippet() { + using (var solution = BasicProject.Generate().ToVs()) { + foreach (var snippet in BasicSnippets) { + TestOneSurroundWithSnippet(solution, snippet, "Nodejs"); + + solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestSelected() { + var snippet = new Snippet( + "if", + "if (true) {\r\n $body$\r\n}\r\n", + new Declaration("false", "if (false) {\r\n $body$\r\n}\r\n") + ); + using (var solution = BasicProject.Generate().ToVs()) { + var app = TestOneTabSnippet(solution, snippet); + + Keyboard.Type("testing"); + app.WaitForText("if (false) {\r\n testing\r\n}\r\n"); + + solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); + } + + } + + private static IEditor TestOneSurroundWithSnippet(IVisualStudioInstance solution, Snippet snippet, string category, string body = "nonempty", string file = "nonempty.js") { + Console.WriteLine("Testing: {0}", snippet.Shortcut); + var server = solution.OpenItem("SnippetsTest", file); + server.Select(1, 1, server.Text.Length); + server.Invoke(() => server.TextView.Caret.EnsureVisible()); + server.SetFocus(); + + solution.ExecuteCommand("Edit.SurroundWith"); + Keyboard.Type(category + "\t"); + + return VerifySnippet(snippet, body, server); + } + + private static IEditor TestOneInsertSnippet(IVisualStudioInstance solution, Snippet snippet, string category, string body = "nonempty", string file ="nonempty.js") { + Console.WriteLine("Testing: {0}", snippet.Shortcut); + var server = solution.OpenItem("SnippetsTest", file); + server.Select(1, 1, server.Text.Length); + server.Invoke(() => server.TextView.Caret.EnsureVisible()); + server.SetFocus(); + + solution.ExecuteCommand("Edit.InsertSnippet"); + Keyboard.Type(category + "\t"); + + return VerifySnippet(snippet, body, server); + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestBasicSnippetsTab() { + using (var solution = BasicProject.Generate().ToVs()) { + foreach (var snippet in BasicSnippets) { + TestOneTabSnippet(solution, snippet); + + solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); + } + } + } + + private static IEditor TestOneTabSnippet(IVisualStudioInstance solution, Snippet snippet) { + Console.WriteLine("Testing: {0}", snippet.Shortcut); + var server = solution.OpenItem("SnippetsTest", "server.js"); + server.MoveCaret(1, 1); + server.Invoke(() => server.TextView.Caret.EnsureVisible()); + server.SetFocus(); + + return VerifySnippet(snippet, "", server); + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TestSurroundWithMultiline() { + using (var solution = BasicProject.Generate().ToVs()) { + foreach (var snippet in BasicSnippets) { + TestOneSurroundWithSnippet( + solution, + snippet, + "Nodejs", + "one\r\n two\r\n three", + "multiline.js" + ); + + solution.CloseActiveWindow(vsSaveChanges.vsSaveChangesNo); + } + } + } + + private static IEditor VerifySnippet(Snippet snippet, string body, IEditor server) { + Keyboard.Type(snippet.Shortcut + "\t"); + + server.WaitForText(snippet.Expected.Replace("$body$", body)); + + foreach (var decl in snippet.Declarations) { + Console.WriteLine("Declaration: {0}", decl.Replacement); + Keyboard.Type(decl.Replacement); + Keyboard.Type("\t"); + server.WaitForText(decl.Expected.Replace("$body$", body)); + Keyboard.Type("\t"); + } + Keyboard.Type("\r"); + return server; + } + + } + +} diff --git a/Nodejs/Tests/Core.UI/UITests.cs b/Nodejs/Tests/Core.UI/UITests.cs index 3b7b40200..cb3052317 100644 --- a/Nodejs/Tests/Core.UI/UITests.cs +++ b/Nodejs/Tests/Core.UI/UITests.cs @@ -1,979 +1,979 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Windows.Automation; -using System.Windows.Input; -using EnvDTE; -using Microsoft.NodejsTools; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TestUtilities; -using TestUtilities.Nodejs; -using TestUtilities.UI; -using Keyboard = TestUtilities.UI.Keyboard; -using Mouse = TestUtilities.UI.Mouse; -using Path = System.IO.Path; - -namespace Microsoft.Nodejs.Tests.UI { - [TestClass] - public class UITests { - [ClassInitialize] - public static void DoDeployment(TestContext context) { - AssertListener.Initialize(); - NodejsTestData.Deploy(); - } - -#if FALSE // Deferred projects currently aren't enabled - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void DeferredSaveWithDot() { - // http://pytools.codeplex.com/workitem/623 - // enable deferred saving on projects - - using (var app = new VisualStudioApp()) { - var props = app.Dte.get_Properties("Environment", "ProjectsAndSolution"); - var prevValue = props.Item("SaveNewProjects").Value; - props.Item("SaveNewProjects").Value = false; - app.OnDispose(() => props.Item("SaveNewProjects").Value = prevValue); - - // now run the test - var newProjDialog = app.FileNewProject(); - - newProjDialog.FocusLanguageNode("JavaScript"); - - var consoleApp = newProjDialog.ProjectTypes.FindItem("Blank Node.js Application"); - consoleApp.Select(); - newProjDialog.ProjectName = "Fob.Baz"; - newProjDialog.ClickOK(); - - // wait for new solution to load... - for (int i = 0; i < 100 && app.Dte.Solution.Projects.Count == 0; i++) { - System.Threading.Thread.Sleep(1000); - } - - TestUtils.DteExecuteCommandOnThreadPool("File.SaveAll"); - - var saveProjDialog = new SaveProjectDialog(app.WaitForDialog()); - saveProjDialog.Save(); - - app.WaitForDialogDismissed(); - - var fullname = app.Dte.Solution.FullName; - app.Dte.Solution.Close(false); - - Directory.Delete(Path.GetDirectoryName(fullname), true); - } - } -#endif - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AbsolutePaths() { - var proj = File.ReadAllText(TestData.GetPath(@"TestData\NodejsProjectData\AbsolutePath\AbsolutePath.njsproj")); - proj = proj.Replace("[ABSPATH]", TestData.GetPath(@"TestData\NodejsProjectData\AbsolutePath")); - File.WriteAllText(TestData.GetPath(@"TestData\NodejsProjectData\AbsolutePath\AbsolutePath.njsproj"), proj); - - - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\AbsolutePath.sln"); - - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var programPy = window.WaitForItem("Solution 'AbsolutePath' (1 project)", "AbsolutePath", "server.js"); - Assert.AreNotEqual(null, programPy); - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void MoveStartupFile() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\MoveStartupFile.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var server = window.WaitForItem("Solution 'MoveStartupFile' (1 project)", "HelloWorld", "server.js"); - var folder = window.WaitForItem("Solution 'MoveStartupFile' (1 project)", "HelloWorld", "TestDir"); - - AutomationWrapper.Select(server); - Keyboard.ControlX(); - - AutomationWrapper.Select(folder); - Keyboard.ControlV(); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'MoveStartupFile' (1 project)", "HelloWorld", "TestDir", "server.js")); - - Assert.IsTrue(((string)project.Properties.Item("StartupFile").Value).EndsWith("TestDir\\server.js")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void CopyPasteFile() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\HelloWorld.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var programPy = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "server.js"); - - AutomationWrapper.Select(programPy); - - Keyboard.ControlC(); - Keyboard.ControlV(); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "server - Copy.js")); - - AutomationWrapper.Select(programPy); - Keyboard.ControlC(); - Keyboard.ControlV(); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "server - Copy (2).js")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void CopyPasteRenameFile() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\CopyPasteRenameProject\CopyPasteRenameProject.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var jsFile = window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameJavaScript", "app.js"); - - AutomationWrapper.Select(jsFile); - - Keyboard.ControlC(); - Keyboard.ControlV(); - var copiedJsFile = window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameJavaScript", "app - Copy.js"); - Assert.AreNotEqual(null, copiedJsFile); - - AutomationWrapper.Select(copiedJsFile); - - Keyboard.PressAndRelease(Key.F2); - System.Threading.Thread.Sleep(100); - - Keyboard.Type("renamed"); - Keyboard.PressAndRelease(Key.Enter); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameJavaScript", "renamed.js")); - - - var tsFile = window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameProjectTypeScript", "app.ts"); - - AutomationWrapper.Select(tsFile); - - Keyboard.ControlC(); - Keyboard.ControlV(); - var copiedTsFile = window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameProjectTypeScript", "app - Copy.ts"); - Assert.AreNotEqual(null, copiedTsFile); - - AutomationWrapper.Select(copiedTsFile); - - Keyboard.PressAndRelease(Key.F2); - System.Threading.Thread.Sleep(100); - - Keyboard.Type("renamed"); - Keyboard.PressAndRelease(Key.Enter); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameProjectTypeScript", "renamed.ts")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void DeleteFile() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\DeleteFile.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var programPy = window.WaitForItem("Solution 'DeleteFile' (1 project)", "HelloWorld", "server.js"); - Assert.IsTrue(File.Exists(@"TestData\NodejsProjectData\DeleteFile\server.js")); - AutomationWrapper.Select(programPy); - - Keyboard.Type(Key.Delete); - app.WaitForDialog(); - VisualStudioApp.CheckMessageBox(MessageBoxButton.Ok, "will be deleted permanently"); - app.WaitForDialogDismissed(); - - window.WaitForItemRemoved("Solution 'DeleteFile' (1 project)", "HelloWorld", "server.js"); - - Assert.IsFalse(File.Exists(@"TestData\NodejsProjectData\DeleteFile\server.js")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AddNewFolder() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\HelloWorld.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld"); - AutomationWrapper.Select(projectNode); - - var startingDirs = new HashSet(Directory.GetDirectories(@"TestData\NodejsProjectData\HelloWorld"), StringComparer.OrdinalIgnoreCase); - Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); - Keyboard.PressAndRelease(Key.D); - Keyboard.PressAndRelease(Key.Right); - Keyboard.PressAndRelease(Key.D); - Keyboard.Type("MyNewFolder"); - var curDirs = new HashSet(Directory.GetDirectories(@"TestData\NodejsProjectData\HelloWorld"), StringComparer.OrdinalIgnoreCase); - Assert.IsTrue(curDirs.IsSubsetOf(startingDirs) && startingDirs.IsSubsetOf(curDirs), "new directory created" + String.Join(", ", curDirs) + " vs. " + String.Join(", ", startingDirs)); - - Keyboard.PressAndRelease(Key.Enter); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "MyNewFolder")); - - Assert.IsTrue(Directory.Exists(@"TestData\NodejsProjectData\HelloWorld\MyNewFolder")); - } - } - } - - /// - /// Adds a new folder which fits exactly w/ no space left in the path name - /// - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AddNewFolderLongPathBoundary() { - using (var app = new VisualStudioApp()) { - var project = OpenLongFileNameProject(app, 24); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); - AutomationWrapper.Select(projectNode); - - Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); - Keyboard.PressAndRelease(Key.D); - Keyboard.PressAndRelease(Key.Right); - Keyboard.PressAndRelease(Key.D); - Keyboard.Type("01234567891"); - Keyboard.PressAndRelease(Key.Enter); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN", "01234567891")); - - var projectLoc = Path.GetDirectoryName(project.FullName); - var checkedPath = Path.Combine(projectLoc, "LongFileNames", "01234567891"); - - Assert.IsTrue(Directory.Exists(checkedPath), checkedPath + " does not exist"); - } - } - } - - /// - /// Adds a new folder with a path that's too long, typing a new path. - /// - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AddNewFolderLongPathTooLong() { - using (var app = new VisualStudioApp()) { - var project = OpenLongFileNameProject(app, 24); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); - AutomationWrapper.Select(projectNode); - - Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); - Keyboard.PressAndRelease(Key.D); - Keyboard.PressAndRelease(Key.Right); - Keyboard.PressAndRelease(Key.D); - Keyboard.Type("012345678912"); - Keyboard.PressAndRelease(Key.Enter); - - VisualStudioApp.CheckMessageBox("The filename or extension is too long."); - } - } - } - - /// - /// Adds a folder with a path that's too long using the default provided folder name. - /// - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AddNewFolderLongPathTooLongCancelEdit() { - using (var app = new VisualStudioApp()) { - var project = OpenLongFileNameProject(app, 21); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); - AutomationWrapper.Select(projectNode); - - Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); - Keyboard.PressAndRelease(Key.D); - Keyboard.PressAndRelease(Key.Right); - Keyboard.PressAndRelease(Key.D); - Keyboard.PressAndRelease(Key.Escape); - - VisualStudioApp.CheckMessageBox("The filename or extension is too long."); - } - } - } - - /// - /// Adds a folder with a path that's too long using the default provided folder name. - /// - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AddNewItemLongPathBoundary() { - using (var app = new VisualStudioApp()) { - var project = OpenLongFileNameProject(app, 12); - - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); - AutomationWrapper.Select(projectNode); - - using (var newItem = NewItemDialog.FromDte(app)) { - newItem.FileName = "NewJSFil.js"; - newItem.OK(); - } - - Assert.IsNotNull(window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN", "NewJSFil.js")); - Assert.IsTrue(File.Exists(Path.Combine(Path.GetDirectoryName(project.FullName), "LongFileNames", "NewJSFil.js"))); - } - } - - /// - /// Adds a folder with a path that's too long using the default provided folder name. - /// - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AddNewItemLongPathTooLong() { - using (var app = new VisualStudioApp()) { - var project = OpenLongFileNameProject(app, 12); - - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); - AutomationWrapper.Select(projectNode); - - using (var newItem = NewItemDialog.FromDte(app)) { - newItem.FileName = "NewJSFile.js"; - newItem.OK(); - } - - VisualStudioApp.CheckMessageBox("The filename or extension is too long."); - } - } - - /// - /// Adds a folder with a path that's too long using the default provided folder name. - /// - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void DeleteLockedFolder() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\DeleteLockedFolder.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folderNode = window.WaitForItem("Solution 'DeleteLockedFolder' (1 project)", "DeleteLockedFolder", "Folder"); - AutomationWrapper.Select(folderNode); - - var psi = new ProcessStartInfo( - Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "system32", "cmd.exe")); - psi.WorkingDirectory = Path.Combine(Environment.CurrentDirectory, @"TestData\NodejsProjectData\DeleteLockedFolder\Folder"); - psi.CreateNoWindow = true; - psi.UseShellExecute = false; - using (var process = System.Diagnostics.Process.Start(psi)) { - try { - //Ensure the other process started and has time to lock the file - System.Threading.Thread.Sleep(1000); - Keyboard.Type(Key.Delete); - app.WaitForDialog(); - Keyboard.Type(Key.Enter); - System.Threading.Thread.Sleep(500); - - VisualStudioApp.CheckMessageBox("The process cannot access the file 'Folder' because it is being used by another process."); - } finally { - process.Kill(); - } - } - - Assert.IsNotNull(window.FindItem("Solution 'DeleteLockedFolder' (1 project)", "DeleteLockedFolder", "Folder")); - } - } - } - - internal static Project OpenLongFileNameProject(VisualStudioApp app, int spaceRemaining = 30) { - string testDir = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid().ToString()); - int targetPathLength = 260 - spaceRemaining - "\\LongFileNames\\".Length; - testDir = testDir + new string('X', targetPathLength - testDir.Length); - Console.WriteLine("Creating long file name project ({0}) at: {1}", testDir.Length, testDir); - - Directory.CreateDirectory(testDir); - File.Copy(@"TestData\NodejsProjectData\LongFileNames.sln", Path.Combine(testDir, "LongFileNames.sln")); - File.Copy(@"TestData\NodejsProjectData\LFN.njsproj", Path.Combine(testDir, "LFN.njsproj")); - - FileUtils.CopyDirectory(@"TestData\NodejsProjectData\LongFileNames", Path.Combine(testDir, "LongFileNames")); - - return app.OpenProject(Path.Combine(testDir, "LongFileNames.sln")); - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void AddNewFolderNested() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\HelloWorld.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld"); - AutomationWrapper.Select(projectNode); - - Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); - Keyboard.PressAndRelease(Key.D); - Keyboard.PressAndRelease(Key.Right); - Keyboard.PressAndRelease(Key.D); - Keyboard.Type("FolderX"); - Keyboard.PressAndRelease(Key.Enter); - - var folderNode = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "FolderX"); - - Assert.AreNotEqual(null, folderNode, "failed to find folder X"); - - AutomationWrapper.Select(folderNode); - - Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); - Keyboard.PressAndRelease(Key.D); - Keyboard.PressAndRelease(Key.Right); - Keyboard.PressAndRelease(Key.D); - Keyboard.Type("FolderY"); - Keyboard.PressAndRelease(Key.Enter); - - var innerFolderNode = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "FolderX", "FolderY"); - - Assert.AreNotEqual(null, innerFolderNode, "failed to find folder Y"); - - AutomationWrapper.Select(innerFolderNode); - - var newItem = project.ProjectItems.Item("FolderX").Collection.Item("FolderY").Collection.AddFromFile( - TestData.GetPath(@"TestData\DebuggerProject\BreakpointBreakOn.js") - ); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "FolderX", "FolderY", "BreakpointBreakOn.js"), "failed to find added file"); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void RenameProjectToExisting() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\RenameProjectTestUI.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'RenameProjectTestUI' (1 project)", "HelloWorld"); - - // rename once, cancel renaming to existing file.... - AutomationWrapper.Select(projectNode); - Keyboard.PressAndRelease(Key.F2); - System.Threading.Thread.Sleep(100); - - Keyboard.Type("HelloWorldExisting"); - Keyboard.PressAndRelease(Key.Enter); - - IntPtr dialog = app.WaitForDialog(); - - VisualStudioApp.CheckMessageBox("HelloWorldExisting.njsproj", "overwrite"); - - // rename again, don't cancel... - AutomationWrapper.Select(projectNode); - Keyboard.PressAndRelease(Key.F2); - System.Threading.Thread.Sleep(100); - - Keyboard.Type("HelloWorldExisting"); - Keyboard.PressAndRelease(Key.Enter); - - dialog = app.WaitForDialog(); - - VisualStudioApp.CheckMessageBox(MessageBoxButton.Yes, "HelloWorldExisting.njsproj", "overwrite"); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'RenameProjectTestUI' (1 project)", "HelloWorldExisting")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void RenameItemsTest() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\RenameItemsTestUI.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - // find server.js, send copy & paste, verify copy of file is there - var projectNode = window.WaitForItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "server.js"); - - // rename once, cancel renaming to existing file.... - AutomationWrapper.Select(projectNode); - Keyboard.PressAndRelease(Key.F2); - System.Threading.Thread.Sleep(100); - - Keyboard.Type("NewName.txt"); - Keyboard.Type(Key.Delete); // delete extension left at end - Keyboard.Type(Key.Delete); - Keyboard.Type(Key.Delete); - System.Threading.Thread.Sleep(100); - Keyboard.PressAndRelease(Key.Enter); - - IntPtr dialog = app.WaitForDialog(); - - VisualStudioApp.CheckMessageBox(MessageBoxButton.Cancel, "file name extension"); - - // rename again, don't cancel... - AutomationWrapper.Select(projectNode); - Keyboard.PressAndRelease(Key.F2); - System.Threading.Thread.Sleep(100); - - Keyboard.Type("NewName.txt"); - Keyboard.Type(Key.Delete); // delete extension left at end - Keyboard.Type(Key.Delete); - Keyboard.Type(Key.Delete); - System.Threading.Thread.Sleep(100); - Keyboard.PressAndRelease(Key.Enter); - - dialog = app.WaitForDialog(); - - VisualStudioApp.CheckMessageBox(MessageBoxButton.Yes, "file name extension"); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "NewName.txt")); - - var subJs = window.WaitForItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "Sub1", "Sub2", "Foo.js"); - Assert.IsNotNull(subJs); - - var sub1 = window.FindItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "Sub1"); - AutomationWrapper.Select(sub1); - Keyboard.PressAndRelease(Key.F2); - System.Threading.Thread.Sleep(100); - - Keyboard.Type("FolderName"); - Keyboard.PressAndRelease(Key.Enter); - - for (int i = 0; i < 20; i++) { - try { - if (project.GetIsFolderExpanded("FolderName")) { - break; - } - } catch (ArgumentException) { - } - System.Threading.Thread.Sleep(100); - } - - Assert.IsTrue(project.GetIsFolderExpanded("FolderName")); - Assert.AreNotEqual(null, window.WaitForItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "FolderName", "Sub2", "Foo.js")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void CrossProjectCopy() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\HelloWorld2.sln", expectedProjects: 2); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folderNode = window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder3"); - AutomationWrapper.Select(folderNode); - - Keyboard.ControlC(); - - var projectNode = window.FindItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld"); - - AutomationWrapper.Select(projectNode); - Keyboard.ControlV(); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld", "TestFolder3")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void CrossProjectCutPaste() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\HelloWorld2.sln", expectedProjects: 2); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folderNode = window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder2"); - AutomationWrapper.Select(folderNode); - - Keyboard.ControlX(); - - var projectNode = window.FindItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld"); - - AutomationWrapper.Select(projectNode); - Keyboard.ControlV(); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld", "TestFolder2")); - Assert.AreEqual(null, window.WaitForItemRemoved("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder2")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void CutPaste() { - using (var app = new VisualStudioApp()) { - - app.OpenProject(@"TestData\NodejsProjectData\HelloWorld2.sln", expectedProjects: 2); - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var subItem = window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder", "SubItem.js"); - AutomationWrapper.Select(subItem); - - Keyboard.ControlX(); - - var projectNode = window.FindItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2"); - - AutomationWrapper.Select(projectNode); - Keyboard.ControlV(); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "SubItem.js")); - Assert.AreEqual(null, window.WaitForItemRemoved("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder", "SubItem.js")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void CopyFolderOnToSelf() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\HelloWorld2.sln", expectedProjects: 2); - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folder = window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder"); - AutomationWrapper.Select(folder); - - Keyboard.ControlC(); - AutomationWrapper.Select(folder); - Keyboard.ControlV(); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder - Copy")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void DragDropTest() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folder = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem.js"); - var point = folder.GetClickablePoint(); - Mouse.MoveTo(point); - Mouse.Down(MouseButton.Left); - - var project = window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest"); - point = project.GetClickablePoint(); - Mouse.MoveTo(point); - Mouse.Up(MouseButton.Left); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "SubItem.js")); - } - } - } - - /// - /// Drag a file onto another file in the same directory, nothing should happen - /// - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void DragDropFileToFileTest() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folder = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem2.js"); - var point = folder.GetClickablePoint(); - Mouse.MoveTo(point); - Mouse.Down(MouseButton.Left); - - var project = window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem3.js"); - point = project.GetClickablePoint(); - Mouse.MoveTo(point); - Mouse.Up(MouseButton.Left); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem2.js")); - Assert.AreNotEqual(null, window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem3.js")); - } - } - } - - /// - /// Drag a file onto it's containing folder, nothing should happen - /// - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void DragDropFileToContainingFolderTest() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folder = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem2.js"); - var point = folder.GetClickablePoint(); - Mouse.MoveTo(point); - Mouse.Down(MouseButton.Left); - - var project = window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder"); - point = project.GetClickablePoint(); - Mouse.MoveTo(point); - Mouse.Up(MouseButton.Left); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem2.js")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void DragLeaveTest() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var item = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder2", "SubItem.js"); - var project = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest"); - - // click on SubItem.js - var point = item.GetClickablePoint(); - Mouse.MoveTo(point); - Mouse.Down(MouseButton.Left); - - // move to project and hover - var projectPoint = project.GetClickablePoint(); - Mouse.MoveTo(projectPoint); - - // move back and release - Mouse.MoveTo(point); - Mouse.Up(MouseButton.Left); - - Assert.AreNotEqual(null, window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder2", "SubItem.js")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void DragLeaveFolderTest() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folder = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder2", "SubFolder"); - var project = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest"); - - // click on SubItem.js - var point = folder.GetClickablePoint(); - Mouse.MoveTo(point); - Mouse.Down(MouseButton.Left); - - // move to project and hover - var projectPoint = project.GetClickablePoint(); - Mouse.MoveTo(projectPoint); - - // move back and release - Mouse.MoveTo(point); - Mouse.Up(MouseButton.Left); - - Assert.AreNotEqual(null, window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder2", "SubFolder")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void MultiSelectCopyAndPaste() { - using (var app = new VisualStudioApp()) { - app.OpenProject(@"TestData\NodejsProjectData\MultiSelectCopyAndPaste.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var window = app.OpenSolutionExplorer(); - - var folderNode = window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste", "server.js"); - Mouse.MoveTo(folderNode.GetClickablePoint()); - Mouse.Click(); - - Keyboard.Press(Key.LeftShift); - Keyboard.PressAndRelease(Key.Down); - Keyboard.PressAndRelease(Key.Down); - Keyboard.Release(Key.LeftShift); - Keyboard.ControlC(); - - var projectNode = window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste"); - - AutomationWrapper.Select(projectNode); - Keyboard.ControlV(); - - Assert.AreNotEqual(null, window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste", "server - Copy.js")); - Assert.AreNotEqual(null, window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste", "server2 - Copy.js")); - Assert.AreNotEqual(null, window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste", "server3 - Copy.js")); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void TransferItem() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\HelloWorld.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - string filename, basename; - int i = 0; - do { - i++; - basename = "test" + i + " .js"; - filename = Path.Combine(Path.GetTempPath(), basename); - } while (System.IO.File.Exists(filename)); - - System.IO.File.WriteAllText(filename, "function f() { }"); - - var fileWindow = app.Dte.ItemOperations.OpenFile(filename); - - using (var dlg = ChooseLocationDialog.FromDte(app)) { - dlg.SelectProject("HelloWorld"); - dlg.OK(); - } - - var window = app.OpenSolutionExplorer(); - Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", basename)); - - Assert.AreEqual(fileWindow.Caption, basename); - - System.IO.File.Delete(filename); - } - } - } - - [Ignore] - [TestMethod, Priority(0), TestCategory("Core")] - [HostType("VSTestHost")] - public void SaveAs() { - using (var app = new VisualStudioApp()) { - var project = app.OpenProject(@"TestData\NodejsProjectData\SaveAsUI.sln"); - - using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { - var solutionTree = app.OpenSolutionExplorer(); - - // open and edit the file - var folderNode = solutionTree.WaitForItem("Solution 'SaveAsUI' (1 project)", "HelloWorld", "server.js"); - folderNode.SetFocus(); - Keyboard.PressAndRelease(Key.Enter); - - var item = project.ProjectItems.Item("server.js"); - var window = item.Open(); - window.Activate(); - - var selection = ((TextSelection)window.Selection); - selection.SelectAll(); - selection.Delete(); - - // save under a new file name - var saveDialog = app.SaveAs(); - string oldName = saveDialog.FileName; - saveDialog.FileName = "Program2.js"; - saveDialog.Save(); - - Assert.AreNotEqual(null, solutionTree.WaitForItem("Solution 'SaveAsUI' (1 project)", "HelloWorld", "Program2.js")); - } - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Windows.Automation; +using System.Windows.Input; +using EnvDTE; +using Microsoft.NodejsTools; +using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; +using TestUtilities.Nodejs; +using TestUtilities.UI; +using Keyboard = TestUtilities.UI.Keyboard; +using Mouse = TestUtilities.UI.Mouse; +using Path = System.IO.Path; + +namespace Microsoft.Nodejs.Tests.UI { + [TestClass] + public class UITests { + [ClassInitialize] + public static void DoDeployment(TestContext context) { + AssertListener.Initialize(); + NodejsTestData.Deploy(); + } + +#if FALSE // Deferred projects currently aren't enabled + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void DeferredSaveWithDot() { + // http://pytools.codeplex.com/workitem/623 + // enable deferred saving on projects + + using (var app = new VisualStudioApp()) { + var props = app.Dte.get_Properties("Environment", "ProjectsAndSolution"); + var prevValue = props.Item("SaveNewProjects").Value; + props.Item("SaveNewProjects").Value = false; + app.OnDispose(() => props.Item("SaveNewProjects").Value = prevValue); + + // now run the test + var newProjDialog = app.FileNewProject(); + + newProjDialog.FocusLanguageNode("JavaScript"); + + var consoleApp = newProjDialog.ProjectTypes.FindItem("Blank Node.js Application"); + consoleApp.Select(); + newProjDialog.ProjectName = "Fob.Baz"; + newProjDialog.ClickOK(); + + // wait for new solution to load... + for (int i = 0; i < 100 && app.Dte.Solution.Projects.Count == 0; i++) { + System.Threading.Thread.Sleep(1000); + } + + TestUtils.DteExecuteCommandOnThreadPool("File.SaveAll"); + + var saveProjDialog = new SaveProjectDialog(app.WaitForDialog()); + saveProjDialog.Save(); + + app.WaitForDialogDismissed(); + + var fullname = app.Dte.Solution.FullName; + app.Dte.Solution.Close(false); + + Directory.Delete(Path.GetDirectoryName(fullname), true); + } + } +#endif + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AbsolutePaths() { + var proj = File.ReadAllText(TestData.GetPath(@"TestData\NodejsProjectData\AbsolutePath\AbsolutePath.njsproj")); + proj = proj.Replace("[ABSPATH]", TestData.GetPath(@"TestData\NodejsProjectData\AbsolutePath")); + File.WriteAllText(TestData.GetPath(@"TestData\NodejsProjectData\AbsolutePath\AbsolutePath.njsproj"), proj); + + + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\AbsolutePath.sln"); + + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var programPy = window.WaitForItem("Solution 'AbsolutePath' (1 project)", "AbsolutePath", "server.js"); + Assert.AreNotEqual(null, programPy); + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void MoveStartupFile() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\MoveStartupFile.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var server = window.WaitForItem("Solution 'MoveStartupFile' (1 project)", "HelloWorld", "server.js"); + var folder = window.WaitForItem("Solution 'MoveStartupFile' (1 project)", "HelloWorld", "TestDir"); + + AutomationWrapper.Select(server); + Keyboard.ControlX(); + + AutomationWrapper.Select(folder); + Keyboard.ControlV(); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'MoveStartupFile' (1 project)", "HelloWorld", "TestDir", "server.js")); + + Assert.IsTrue(((string)project.Properties.Item("StartupFile").Value).EndsWith("TestDir\\server.js")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void CopyPasteFile() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\HelloWorld.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var programPy = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "server.js"); + + AutomationWrapper.Select(programPy); + + Keyboard.ControlC(); + Keyboard.ControlV(); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "server - Copy.js")); + + AutomationWrapper.Select(programPy); + Keyboard.ControlC(); + Keyboard.ControlV(); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "server - Copy (2).js")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void CopyPasteRenameFile() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\CopyPasteRenameProject\CopyPasteRenameProject.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var jsFile = window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameJavaScript", "app.js"); + + AutomationWrapper.Select(jsFile); + + Keyboard.ControlC(); + Keyboard.ControlV(); + var copiedJsFile = window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameJavaScript", "app - Copy.js"); + Assert.AreNotEqual(null, copiedJsFile); + + AutomationWrapper.Select(copiedJsFile); + + Keyboard.PressAndRelease(Key.F2); + System.Threading.Thread.Sleep(100); + + Keyboard.Type("renamed"); + Keyboard.PressAndRelease(Key.Enter); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameJavaScript", "renamed.js")); + + + var tsFile = window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameProjectTypeScript", "app.ts"); + + AutomationWrapper.Select(tsFile); + + Keyboard.ControlC(); + Keyboard.ControlV(); + var copiedTsFile = window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameProjectTypeScript", "app - Copy.ts"); + Assert.AreNotEqual(null, copiedTsFile); + + AutomationWrapper.Select(copiedTsFile); + + Keyboard.PressAndRelease(Key.F2); + System.Threading.Thread.Sleep(100); + + Keyboard.Type("renamed"); + Keyboard.PressAndRelease(Key.Enter); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'CopyPasteRenameProject' (2 projects)", "CopyPasteRenameProjectTypeScript", "renamed.ts")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void DeleteFile() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\DeleteFile.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var programPy = window.WaitForItem("Solution 'DeleteFile' (1 project)", "HelloWorld", "server.js"); + Assert.IsTrue(File.Exists(@"TestData\NodejsProjectData\DeleteFile\server.js")); + AutomationWrapper.Select(programPy); + + Keyboard.Type(Key.Delete); + app.WaitForDialog(); + VisualStudioApp.CheckMessageBox(MessageBoxButton.Ok, "will be deleted permanently"); + app.WaitForDialogDismissed(); + + window.WaitForItemRemoved("Solution 'DeleteFile' (1 project)", "HelloWorld", "server.js"); + + Assert.IsFalse(File.Exists(@"TestData\NodejsProjectData\DeleteFile\server.js")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AddNewFolder() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\HelloWorld.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld"); + AutomationWrapper.Select(projectNode); + + var startingDirs = new HashSet(Directory.GetDirectories(@"TestData\NodejsProjectData\HelloWorld"), StringComparer.OrdinalIgnoreCase); + Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); + Keyboard.PressAndRelease(Key.D); + Keyboard.PressAndRelease(Key.Right); + Keyboard.PressAndRelease(Key.D); + Keyboard.Type("MyNewFolder"); + var curDirs = new HashSet(Directory.GetDirectories(@"TestData\NodejsProjectData\HelloWorld"), StringComparer.OrdinalIgnoreCase); + Assert.IsTrue(curDirs.IsSubsetOf(startingDirs) && startingDirs.IsSubsetOf(curDirs), "new directory created" + String.Join(", ", curDirs) + " vs. " + String.Join(", ", startingDirs)); + + Keyboard.PressAndRelease(Key.Enter); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "MyNewFolder")); + + Assert.IsTrue(Directory.Exists(@"TestData\NodejsProjectData\HelloWorld\MyNewFolder")); + } + } + } + + /// + /// Adds a new folder which fits exactly w/ no space left in the path name + /// + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AddNewFolderLongPathBoundary() { + using (var app = new VisualStudioApp()) { + var project = OpenLongFileNameProject(app, 24); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); + AutomationWrapper.Select(projectNode); + + Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); + Keyboard.PressAndRelease(Key.D); + Keyboard.PressAndRelease(Key.Right); + Keyboard.PressAndRelease(Key.D); + Keyboard.Type("01234567891"); + Keyboard.PressAndRelease(Key.Enter); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN", "01234567891")); + + var projectLoc = Path.GetDirectoryName(project.FullName); + var checkedPath = Path.Combine(projectLoc, "LongFileNames", "01234567891"); + + Assert.IsTrue(Directory.Exists(checkedPath), checkedPath + " does not exist"); + } + } + } + + /// + /// Adds a new folder with a path that's too long, typing a new path. + /// + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AddNewFolderLongPathTooLong() { + using (var app = new VisualStudioApp()) { + var project = OpenLongFileNameProject(app, 24); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); + AutomationWrapper.Select(projectNode); + + Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); + Keyboard.PressAndRelease(Key.D); + Keyboard.PressAndRelease(Key.Right); + Keyboard.PressAndRelease(Key.D); + Keyboard.Type("012345678912"); + Keyboard.PressAndRelease(Key.Enter); + + VisualStudioApp.CheckMessageBox("The filename or extension is too long."); + } + } + } + + /// + /// Adds a folder with a path that's too long using the default provided folder name. + /// + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AddNewFolderLongPathTooLongCancelEdit() { + using (var app = new VisualStudioApp()) { + var project = OpenLongFileNameProject(app, 21); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); + AutomationWrapper.Select(projectNode); + + Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); + Keyboard.PressAndRelease(Key.D); + Keyboard.PressAndRelease(Key.Right); + Keyboard.PressAndRelease(Key.D); + Keyboard.PressAndRelease(Key.Escape); + + VisualStudioApp.CheckMessageBox("The filename or extension is too long."); + } + } + } + + /// + /// Adds a folder with a path that's too long using the default provided folder name. + /// + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AddNewItemLongPathBoundary() { + using (var app = new VisualStudioApp()) { + var project = OpenLongFileNameProject(app, 12); + + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); + AutomationWrapper.Select(projectNode); + + using (var newItem = NewItemDialog.FromDte(app)) { + newItem.FileName = "NewJSFil.js"; + newItem.OK(); + } + + Assert.IsNotNull(window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN", "NewJSFil.js")); + Assert.IsTrue(File.Exists(Path.Combine(Path.GetDirectoryName(project.FullName), "LongFileNames", "NewJSFil.js"))); + } + } + + /// + /// Adds a folder with a path that's too long using the default provided folder name. + /// + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AddNewItemLongPathTooLong() { + using (var app = new VisualStudioApp()) { + var project = OpenLongFileNameProject(app, 12); + + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'LongFileNames' (1 project)", "LFN"); + AutomationWrapper.Select(projectNode); + + using (var newItem = NewItemDialog.FromDte(app)) { + newItem.FileName = "NewJSFile.js"; + newItem.OK(); + } + + VisualStudioApp.CheckMessageBox("The filename or extension is too long."); + } + } + + /// + /// Adds a folder with a path that's too long using the default provided folder name. + /// + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void DeleteLockedFolder() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\DeleteLockedFolder.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folderNode = window.WaitForItem("Solution 'DeleteLockedFolder' (1 project)", "DeleteLockedFolder", "Folder"); + AutomationWrapper.Select(folderNode); + + var psi = new ProcessStartInfo( + Path.Combine(Environment.GetEnvironmentVariable("WINDIR"), "system32", "cmd.exe")); + psi.WorkingDirectory = Path.Combine(Environment.CurrentDirectory, @"TestData\NodejsProjectData\DeleteLockedFolder\Folder"); + psi.CreateNoWindow = true; + psi.UseShellExecute = false; + using (var process = System.Diagnostics.Process.Start(psi)) { + try { + //Ensure the other process started and has time to lock the file + System.Threading.Thread.Sleep(1000); + Keyboard.Type(Key.Delete); + app.WaitForDialog(); + Keyboard.Type(Key.Enter); + System.Threading.Thread.Sleep(500); + + VisualStudioApp.CheckMessageBox("The process cannot access the file 'Folder' because it is being used by another process."); + } finally { + process.Kill(); + } + } + + Assert.IsNotNull(window.FindItem("Solution 'DeleteLockedFolder' (1 project)", "DeleteLockedFolder", "Folder")); + } + } + } + + internal static Project OpenLongFileNameProject(VisualStudioApp app, int spaceRemaining = 30) { + string testDir = Path.Combine(Environment.CurrentDirectory, Guid.NewGuid().ToString()); + int targetPathLength = 260 - spaceRemaining - "\\LongFileNames\\".Length; + testDir = testDir + new string('X', targetPathLength - testDir.Length); + Console.WriteLine("Creating long file name project ({0}) at: {1}", testDir.Length, testDir); + + Directory.CreateDirectory(testDir); + File.Copy(@"TestData\NodejsProjectData\LongFileNames.sln", Path.Combine(testDir, "LongFileNames.sln")); + File.Copy(@"TestData\NodejsProjectData\LFN.njsproj", Path.Combine(testDir, "LFN.njsproj")); + + FileUtils.CopyDirectory(@"TestData\NodejsProjectData\LongFileNames", Path.Combine(testDir, "LongFileNames")); + + return app.OpenProject(Path.Combine(testDir, "LongFileNames.sln")); + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void AddNewFolderNested() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\HelloWorld.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld"); + AutomationWrapper.Select(projectNode); + + Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); + Keyboard.PressAndRelease(Key.D); + Keyboard.PressAndRelease(Key.Right); + Keyboard.PressAndRelease(Key.D); + Keyboard.Type("FolderX"); + Keyboard.PressAndRelease(Key.Enter); + + var folderNode = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "FolderX"); + + Assert.AreNotEqual(null, folderNode, "failed to find folder X"); + + AutomationWrapper.Select(folderNode); + + Keyboard.PressAndRelease(Key.F10, Key.LeftCtrl, Key.LeftShift); + Keyboard.PressAndRelease(Key.D); + Keyboard.PressAndRelease(Key.Right); + Keyboard.PressAndRelease(Key.D); + Keyboard.Type("FolderY"); + Keyboard.PressAndRelease(Key.Enter); + + var innerFolderNode = window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "FolderX", "FolderY"); + + Assert.AreNotEqual(null, innerFolderNode, "failed to find folder Y"); + + AutomationWrapper.Select(innerFolderNode); + + var newItem = project.ProjectItems.Item("FolderX").Collection.Item("FolderY").Collection.AddFromFile( + TestData.GetPath(@"TestData\DebuggerProject\BreakpointBreakOn.js") + ); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", "FolderX", "FolderY", "BreakpointBreakOn.js"), "failed to find added file"); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void RenameProjectToExisting() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\RenameProjectTestUI.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'RenameProjectTestUI' (1 project)", "HelloWorld"); + + // rename once, cancel renaming to existing file.... + AutomationWrapper.Select(projectNode); + Keyboard.PressAndRelease(Key.F2); + System.Threading.Thread.Sleep(100); + + Keyboard.Type("HelloWorldExisting"); + Keyboard.PressAndRelease(Key.Enter); + + IntPtr dialog = app.WaitForDialog(); + + VisualStudioApp.CheckMessageBox("HelloWorldExisting.njsproj", "overwrite"); + + // rename again, don't cancel... + AutomationWrapper.Select(projectNode); + Keyboard.PressAndRelease(Key.F2); + System.Threading.Thread.Sleep(100); + + Keyboard.Type("HelloWorldExisting"); + Keyboard.PressAndRelease(Key.Enter); + + dialog = app.WaitForDialog(); + + VisualStudioApp.CheckMessageBox(MessageBoxButton.Yes, "HelloWorldExisting.njsproj", "overwrite"); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'RenameProjectTestUI' (1 project)", "HelloWorldExisting")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void RenameItemsTest() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\RenameItemsTestUI.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + // find server.js, send copy & paste, verify copy of file is there + var projectNode = window.WaitForItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "server.js"); + + // rename once, cancel renaming to existing file.... + AutomationWrapper.Select(projectNode); + Keyboard.PressAndRelease(Key.F2); + System.Threading.Thread.Sleep(100); + + Keyboard.Type("NewName.txt"); + Keyboard.Type(Key.Delete); // delete extension left at end + Keyboard.Type(Key.Delete); + Keyboard.Type(Key.Delete); + System.Threading.Thread.Sleep(100); + Keyboard.PressAndRelease(Key.Enter); + + IntPtr dialog = app.WaitForDialog(); + + VisualStudioApp.CheckMessageBox(MessageBoxButton.Cancel, "file name extension"); + + // rename again, don't cancel... + AutomationWrapper.Select(projectNode); + Keyboard.PressAndRelease(Key.F2); + System.Threading.Thread.Sleep(100); + + Keyboard.Type("NewName.txt"); + Keyboard.Type(Key.Delete); // delete extension left at end + Keyboard.Type(Key.Delete); + Keyboard.Type(Key.Delete); + System.Threading.Thread.Sleep(100); + Keyboard.PressAndRelease(Key.Enter); + + dialog = app.WaitForDialog(); + + VisualStudioApp.CheckMessageBox(MessageBoxButton.Yes, "file name extension"); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "NewName.txt")); + + var subJs = window.WaitForItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "Sub1", "Sub2", "Foo.js"); + Assert.IsNotNull(subJs); + + var sub1 = window.FindItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "Sub1"); + AutomationWrapper.Select(sub1); + Keyboard.PressAndRelease(Key.F2); + System.Threading.Thread.Sleep(100); + + Keyboard.Type("FolderName"); + Keyboard.PressAndRelease(Key.Enter); + + for (int i = 0; i < 20; i++) { + try { + if (project.GetIsFolderExpanded("FolderName")) { + break; + } + } catch (ArgumentException) { + } + System.Threading.Thread.Sleep(100); + } + + Assert.IsTrue(project.GetIsFolderExpanded("FolderName")); + Assert.AreNotEqual(null, window.WaitForItem("Solution 'RenameItemsTestUI' (1 project)", "HelloWorld", "FolderName", "Sub2", "Foo.js")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void CrossProjectCopy() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\HelloWorld2.sln", expectedProjects: 2); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folderNode = window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder3"); + AutomationWrapper.Select(folderNode); + + Keyboard.ControlC(); + + var projectNode = window.FindItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld"); + + AutomationWrapper.Select(projectNode); + Keyboard.ControlV(); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld", "TestFolder3")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void CrossProjectCutPaste() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\HelloWorld2.sln", expectedProjects: 2); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folderNode = window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder2"); + AutomationWrapper.Select(folderNode); + + Keyboard.ControlX(); + + var projectNode = window.FindItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld"); + + AutomationWrapper.Select(projectNode); + Keyboard.ControlV(); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld", "TestFolder2")); + Assert.AreEqual(null, window.WaitForItemRemoved("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder2")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void CutPaste() { + using (var app = new VisualStudioApp()) { + + app.OpenProject(@"TestData\NodejsProjectData\HelloWorld2.sln", expectedProjects: 2); + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var subItem = window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder", "SubItem.js"); + AutomationWrapper.Select(subItem); + + Keyboard.ControlX(); + + var projectNode = window.FindItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2"); + + AutomationWrapper.Select(projectNode); + Keyboard.ControlV(); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "SubItem.js")); + Assert.AreEqual(null, window.WaitForItemRemoved("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder", "SubItem.js")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void CopyFolderOnToSelf() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\HelloWorld2.sln", expectedProjects: 2); + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folder = window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder"); + AutomationWrapper.Select(folder); + + Keyboard.ControlC(); + AutomationWrapper.Select(folder); + Keyboard.ControlV(); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld2' (2 projects)", "HelloWorld2", "TestFolder - Copy")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void DragDropTest() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folder = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem.js"); + var point = folder.GetClickablePoint(); + Mouse.MoveTo(point); + Mouse.Down(MouseButton.Left); + + var project = window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest"); + point = project.GetClickablePoint(); + Mouse.MoveTo(point); + Mouse.Up(MouseButton.Left); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "SubItem.js")); + } + } + } + + /// + /// Drag a file onto another file in the same directory, nothing should happen + /// + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void DragDropFileToFileTest() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folder = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem2.js"); + var point = folder.GetClickablePoint(); + Mouse.MoveTo(point); + Mouse.Down(MouseButton.Left); + + var project = window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem3.js"); + point = project.GetClickablePoint(); + Mouse.MoveTo(point); + Mouse.Up(MouseButton.Left); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem2.js")); + Assert.AreNotEqual(null, window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem3.js")); + } + } + } + + /// + /// Drag a file onto it's containing folder, nothing should happen + /// + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void DragDropFileToContainingFolderTest() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folder = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem2.js"); + var point = folder.GetClickablePoint(); + Mouse.MoveTo(point); + Mouse.Down(MouseButton.Left); + + var project = window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder"); + point = project.GetClickablePoint(); + Mouse.MoveTo(point); + Mouse.Up(MouseButton.Left); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder", "SubItem2.js")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void DragLeaveTest() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var item = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder2", "SubItem.js"); + var project = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest"); + + // click on SubItem.js + var point = item.GetClickablePoint(); + Mouse.MoveTo(point); + Mouse.Down(MouseButton.Left); + + // move to project and hover + var projectPoint = project.GetClickablePoint(); + Mouse.MoveTo(projectPoint); + + // move back and release + Mouse.MoveTo(point); + Mouse.Up(MouseButton.Left); + + Assert.AreNotEqual(null, window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder2", "SubItem.js")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void DragLeaveFolderTest() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\DragDropTest.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folder = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder2", "SubFolder"); + var project = window.WaitForItem("Solution 'DragDropTest' (1 project)", "DragDropTest"); + + // click on SubItem.js + var point = folder.GetClickablePoint(); + Mouse.MoveTo(point); + Mouse.Down(MouseButton.Left); + + // move to project and hover + var projectPoint = project.GetClickablePoint(); + Mouse.MoveTo(projectPoint); + + // move back and release + Mouse.MoveTo(point); + Mouse.Up(MouseButton.Left); + + Assert.AreNotEqual(null, window.FindItem("Solution 'DragDropTest' (1 project)", "DragDropTest", "TestFolder2", "SubFolder")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void MultiSelectCopyAndPaste() { + using (var app = new VisualStudioApp()) { + app.OpenProject(@"TestData\NodejsProjectData\MultiSelectCopyAndPaste.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var window = app.OpenSolutionExplorer(); + + var folderNode = window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste", "server.js"); + Mouse.MoveTo(folderNode.GetClickablePoint()); + Mouse.Click(); + + Keyboard.Press(Key.LeftShift); + Keyboard.PressAndRelease(Key.Down); + Keyboard.PressAndRelease(Key.Down); + Keyboard.Release(Key.LeftShift); + Keyboard.ControlC(); + + var projectNode = window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste"); + + AutomationWrapper.Select(projectNode); + Keyboard.ControlV(); + + Assert.AreNotEqual(null, window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste", "server - Copy.js")); + Assert.AreNotEqual(null, window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste", "server2 - Copy.js")); + Assert.AreNotEqual(null, window.WaitForItem("Solution 'MultiSelectCopyAndPaste' (1 project)", "MultiSelectCopyAndPaste", "server3 - Copy.js")); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void TransferItem() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\HelloWorld.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + string filename, basename; + int i = 0; + do { + i++; + basename = "test" + i + " .js"; + filename = Path.Combine(Path.GetTempPath(), basename); + } while (System.IO.File.Exists(filename)); + + System.IO.File.WriteAllText(filename, "function f() { }"); + + var fileWindow = app.Dte.ItemOperations.OpenFile(filename); + + using (var dlg = ChooseLocationDialog.FromDte(app)) { + dlg.SelectProject("HelloWorld"); + dlg.OK(); + } + + var window = app.OpenSolutionExplorer(); + Assert.AreNotEqual(null, window.WaitForItem("Solution 'HelloWorld' (1 project)", "HelloWorld", basename)); + + Assert.AreEqual(fileWindow.Caption, basename); + + System.IO.File.Delete(filename); + } + } + } + + [Ignore] + [TestMethod, Priority(0), TestCategory("Core")] + [HostType("VSTestHost")] + public void SaveAs() { + using (var app = new VisualStudioApp()) { + var project = app.OpenProject(@"TestData\NodejsProjectData\SaveAsUI.sln"); + + using (new NodejsOptionHolder(NodejsPackage.Instance.GeneralOptionsPage, "ShowBrowserAndNodeLabels", false)) { + var solutionTree = app.OpenSolutionExplorer(); + + // open and edit the file + var folderNode = solutionTree.WaitForItem("Solution 'SaveAsUI' (1 project)", "HelloWorld", "server.js"); + folderNode.SetFocus(); + Keyboard.PressAndRelease(Key.Enter); + + var item = project.ProjectItems.Item("server.js"); + var window = item.Open(); + window.Activate(); + + var selection = ((TextSelection)window.Selection); + selection.SelectAll(); + selection.Delete(); + + // save under a new file name + var saveDialog = app.SaveAs(); + string oldName = saveDialog.FileName; + saveDialog.FileName = "Program2.js"; + saveDialog.Save(); + + Assert.AreNotEqual(null, solutionTree.WaitForItem("Solution 'SaveAsUI' (1 project)", "HelloWorld", "Program2.js")); + } + } + } + } +} diff --git a/Nodejs/Tests/Core/ImportWizardTests.cs b/Nodejs/Tests/Core/ImportWizardTests.cs index 6a7b30d58..e93df5deb 100644 --- a/Nodejs/Tests/Core/ImportWizardTests.cs +++ b/Nodejs/Tests/Core/ImportWizardTests.cs @@ -227,8 +227,8 @@ public void ImportWizardEmptyFolders() { AssertUtil.ContainsExactly(proj.Descendants(proj.GetName("Folder")).Select(x => x.Attribute("Include").Value), "Baz"); - } - + } + [TestMethod, Priority(0)] public void ImportWizardExcludeBowerComponents() { var settings = new ImportSettings(); diff --git a/Nodejs/Tests/Core/NodePathResolutionTests.cs b/Nodejs/Tests/Core/NodePathResolutionTests.cs index 7023eaddd..9c344324b 100644 --- a/Nodejs/Tests/Core/NodePathResolutionTests.cs +++ b/Nodejs/Tests/Core/NodePathResolutionTests.cs @@ -15,44 +15,44 @@ //*********************************************************// using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.NodejsTools; -using System.IO; - +using Microsoft.NodejsTools; +using System.IO; + namespace NodejsTests { [TestClass] public class NodePathResolutionTests { [TestMethod, Priority(0)] - public void CheckNodeExeEnvironmentResolution() { - Assert.IsTrue(File.Exists(Nodejs.NodeExePath)); - } - + public void CheckNodeExeEnvironmentResolution() { + Assert.IsTrue(File.Exists(Nodejs.NodeExePath)); + } + [TestMethod, Priority(0)] - public void CheckRelativeNodeExePathResolution() { - var path = @"C:\mynodepath\node.exe"; - var dir = Path.GetDirectoryName(path); - var filename = Path.GetFileName(path); - - Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(dir, filename), - "Resolution failed when relative filename is surrounded by quotes"); - Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(dir, "\"" + filename + "\""), - "Resolution failed when relative filename is surrounded by quotes"); - Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(dir, "./" + filename), - "Resolution failed when relative filename begins with ./"); - Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(dir, "../" + Path.GetFileName(dir) + "/" + filename), - "Resolution failed when relative filename begins with ../"); - Assert.AreEqual(".", Nodejs.GetAbsoluteNodeExePath(null, "."), - "Resolution should return relative path on failure"); - Assert.AreEqual(Nodejs.NodeExePath, Nodejs.GetAbsoluteNodeExePath(dir, null), - "Resolution should fall back to environment path if no relative path is specified"); - } - + public void CheckRelativeNodeExePathResolution() { + var path = @"C:\mynodepath\node.exe"; + var dir = Path.GetDirectoryName(path); + var filename = Path.GetFileName(path); + + Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(dir, filename), + "Resolution failed when relative filename is surrounded by quotes"); + Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(dir, "\"" + filename + "\""), + "Resolution failed when relative filename is surrounded by quotes"); + Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(dir, "./" + filename), + "Resolution failed when relative filename begins with ./"); + Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(dir, "../" + Path.GetFileName(dir) + "/" + filename), + "Resolution failed when relative filename begins with ../"); + Assert.AreEqual(".", Nodejs.GetAbsoluteNodeExePath(null, "."), + "Resolution should return relative path on failure"); + Assert.AreEqual(Nodejs.NodeExePath, Nodejs.GetAbsoluteNodeExePath(dir, null), + "Resolution should fall back to environment path if no relative path is specified"); + } + [TestMethod, Priority(0)] - public void CheckAbsoluteNodeExePathResolution() { - var path = @"C:\Program Files\node.exe"; - var dir = Path.GetDirectoryName(path); - - Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(@"C:\myprojectpath", path), - "Resolution should use absolute path if specified"); + public void CheckAbsoluteNodeExePathResolution() { + var path = @"C:\Program Files\node.exe"; + var dir = Path.GetDirectoryName(path); + + Assert.AreEqual(path, Nodejs.GetAbsoluteNodeExePath(@"C:\myprojectpath", path), + "Resolution should use absolute path if specified"); } } } diff --git a/Nodejs/Tests/Core/SourceMapTests.cs b/Nodejs/Tests/Core/SourceMapTests.cs index aa77616a3..b529c54fc 100644 --- a/Nodejs/Tests/Core/SourceMapTests.cs +++ b/Nodejs/Tests/Core/SourceMapTests.cs @@ -19,8 +19,8 @@ using Microsoft.NodejsTools.SourceMapping; using Microsoft.VisualStudio.TestTools.UnitTesting; using TestUtilities; -using TestUtilities.Nodejs; - +using TestUtilities.Nodejs; + namespace NodejsTests { [TestClass] public class SourceMapTests { @@ -211,12 +211,12 @@ public void TestMapToJavaScript() { } [TestMethod, Priority(0), TestCategory("Debugging")] - public void TestGetOriginalFileNameWithStackFrame() { - string javaScriptFileName = TestData.GetPath(@"TestData\TypeScriptMultfile\all.js"); - var sourceMapper = new SourceMapper(); - int? line = 24, column = 9; - string originalFileName = sourceMapper.GetOriginalFileName(javaScriptFileName, line, column); - Assert.IsTrue(originalFileName.Contains("file2.ts")); - } + public void TestGetOriginalFileNameWithStackFrame() { + string javaScriptFileName = TestData.GetPath(@"TestData\TypeScriptMultfile\all.js"); + var sourceMapper = new SourceMapper(); + int? line = 24, column = 9; + string originalFileName = sourceMapper.GetOriginalFileName(javaScriptFileName, line, column); + Assert.IsTrue(originalFileName.Contains("file2.ts")); + } } } diff --git a/Nodejs/Tests/NpmTests/AbstractFilesystemPackageJsonTests.cs b/Nodejs/Tests/NpmTests/AbstractFilesystemPackageJsonTests.cs index 64c209c8d..eb746a46d 100644 --- a/Nodejs/Tests/NpmTests/AbstractFilesystemPackageJsonTests.cs +++ b/Nodejs/Tests/NpmTests/AbstractFilesystemPackageJsonTests.cs @@ -1,53 +1,53 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.IO; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - public abstract class AbstractFilesystemPackageJsonTests : AbstractPackageJsonTests { - protected TemporaryFileManager TempFileManager { get; private set; } - - [TestInitialize] - public void Init() { - TempFileManager = new TemporaryFileManager(); - } - - [TestCleanup] - public void Cleanup() { - TempFileManager.Dispose(); - } - - protected void CreatePackageJson(string filename, string json) { - using (var fout = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)) { - using (var writer = new StreamWriter(fout)) { - writer.Write(json); - } - } - } - - protected string CreateRootPackageDir() { - return TempFileManager.GetNewTempDirectory().FullName; - } - - protected string CreateRootPackage(string json) { - var dir = CreateRootPackageDir(); - var path = Path.Combine(dir, "package.json"); - CreatePackageJson(path, json); - return dir; - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + public abstract class AbstractFilesystemPackageJsonTests : AbstractPackageJsonTests { + protected TemporaryFileManager TempFileManager { get; private set; } + + [TestInitialize] + public void Init() { + TempFileManager = new TemporaryFileManager(); + } + + [TestCleanup] + public void Cleanup() { + TempFileManager.Dispose(); + } + + protected void CreatePackageJson(string filename, string json) { + using (var fout = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None)) { + using (var writer = new StreamWriter(fout)) { + writer.Write(json); + } + } + } + + protected string CreateRootPackageDir() { + return TempFileManager.GetNewTempDirectory().FullName; + } + + protected string CreateRootPackage(string json) { + var dir = CreateRootPackageDir(); + var path = Path.Combine(dir, "package.json"); + CreatePackageJson(path, json); + return dir; + } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/AbstractPackageJsonTests.cs b/Nodejs/Tests/NpmTests/AbstractPackageJsonTests.cs index e6f507ce3..b22219ea4 100644 --- a/Nodejs/Tests/NpmTests/AbstractPackageJsonTests.cs +++ b/Nodejs/Tests/NpmTests/AbstractPackageJsonTests.cs @@ -1,86 +1,86 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; -using System.IO; -using System.Text; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - public abstract class AbstractPackageJsonTests { - protected const string PkgEmpty = "{}"; - - protected const string PkgSimple = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"" -}"; - - protected IPackageJson LoadFrom(string json) { - return PackageJsonFactory.Create(new MockPackageJsonSource(json)); - } - - protected string LoadStringFromResource(string manifestResourceName) { - using (var reader = new StreamReader(typeof(AbstractPackageJsonTests).Assembly.GetManifestResourceStream(manifestResourceName))) { - return reader.ReadToEnd(); - } - } - - protected IPackageJson LoadFromResource(string manifestResourceName) { - using (var reader = new StreamReader(typeof(AbstractPackageJsonTests).Assembly.GetManifestResourceStream(manifestResourceName))) { - return LoadFrom(reader); - } - } - - protected IPackageJson LoadFromFile(string fullPathToFile) { - return PackageJsonFactory.Create(new FilePackageJsonSource(fullPathToFile)); - } - - protected IPackageJson LoadFrom(TextReader reader) { - return PackageJsonFactory.Create(new ReaderPackageJsonSource(reader)); - } - - private static void CheckContains(ISet retrieved, IEnumerable expected) { - foreach (var value in expected) { - Assert.IsTrue(retrieved.Contains(value), string.Format("Expected to find value '{0}'.", value)); - } - } - - protected static void CheckStringArrayContents( - IPkgStringArray array, - int expectedCount, - IEnumerable expectedValues) { - Assert.IsNotNull(array, "Array should not be null."); - Assert.AreEqual(expectedCount, array.Count, "Value count mismatch."); - - var retrieved = new HashSet(); - foreach (string file in array) { - retrieved.Add(file); - } - CheckContains(retrieved, expectedValues); - - retrieved = new HashSet(); - for (int index = 0, size = array.Count; index < size; ++index) { - retrieved.Add(array[index]); - } - CheckContains(retrieved, expectedValues); - } - - protected static void CheckEmptyArray(IPkgStringArray array) { - CheckStringArrayContents(array, 0, new string[0]); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; +using System.IO; +using System.Text; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + public abstract class AbstractPackageJsonTests { + protected const string PkgEmpty = "{}"; + + protected const string PkgSimple = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"" +}"; + + protected IPackageJson LoadFrom(string json) { + return PackageJsonFactory.Create(new MockPackageJsonSource(json)); + } + + protected string LoadStringFromResource(string manifestResourceName) { + using (var reader = new StreamReader(typeof(AbstractPackageJsonTests).Assembly.GetManifestResourceStream(manifestResourceName))) { + return reader.ReadToEnd(); + } + } + + protected IPackageJson LoadFromResource(string manifestResourceName) { + using (var reader = new StreamReader(typeof(AbstractPackageJsonTests).Assembly.GetManifestResourceStream(manifestResourceName))) { + return LoadFrom(reader); + } + } + + protected IPackageJson LoadFromFile(string fullPathToFile) { + return PackageJsonFactory.Create(new FilePackageJsonSource(fullPathToFile)); + } + + protected IPackageJson LoadFrom(TextReader reader) { + return PackageJsonFactory.Create(new ReaderPackageJsonSource(reader)); + } + + private static void CheckContains(ISet retrieved, IEnumerable expected) { + foreach (var value in expected) { + Assert.IsTrue(retrieved.Contains(value), string.Format("Expected to find value '{0}'.", value)); + } + } + + protected static void CheckStringArrayContents( + IPkgStringArray array, + int expectedCount, + IEnumerable expectedValues) { + Assert.IsNotNull(array, "Array should not be null."); + Assert.AreEqual(expectedCount, array.Count, "Value count mismatch."); + + var retrieved = new HashSet(); + foreach (string file in array) { + retrieved.Add(file); + } + CheckContains(retrieved, expectedValues); + + retrieved = new HashSet(); + for (int index = 0, size = array.Count; index < size; ++index) { + retrieved.Add(array[index]); + } + CheckContains(retrieved, expectedValues); + } + + protected static void CheckEmptyArray(IPkgStringArray array) { + CheckStringArrayContents(array, 0, new string[0]); + } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/FileSystemPackageJsonTests.cs b/Nodejs/Tests/NpmTests/FileSystemPackageJsonTests.cs index f65b7166c..5254aa2a5 100644 --- a/Nodejs/Tests/NpmTests/FileSystemPackageJsonTests.cs +++ b/Nodejs/Tests/NpmTests/FileSystemPackageJsonTests.cs @@ -1,45 +1,45 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.IO; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - [TestClass] - public class FileSystemPackageJsonTests : AbstractFilesystemPackageJsonTests { - [TestMethod, Priority(0)] - public void TestReadFromFile() { - var dir = TempFileManager.GetNewTempDirectory(); - var path = Path.Combine(dir.FullName, "package.json"); - CreatePackageJson(path, PkgSimple); - CheckPackage(PackageJsonFactory.Create(new FilePackageJsonSource(path))); - } - - [TestMethod, Priority(0)] - public void TestReadFromDirectory() { - var dir = TempFileManager.GetNewTempDirectory(); - CreatePackageJson(Path.Combine(dir.FullName, "package.json"), PkgSimple); - CheckPackage(PackageJsonFactory.Create(new DirectoryPackageJsonSource(dir.FullName))); - } - - private static void CheckPackage(IPackageJson pkg) { - Assert.IsNotNull(pkg, "Package should not be null."); - Assert.AreEqual("TestPkg", pkg.Name, "Package name mismatch."); - Assert.AreEqual(SemverVersion.Parse("0.1.0"), pkg.Version, "Package version mismatch."); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.IO; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + [TestClass] + public class FileSystemPackageJsonTests : AbstractFilesystemPackageJsonTests { + [TestMethod, Priority(0)] + public void TestReadFromFile() { + var dir = TempFileManager.GetNewTempDirectory(); + var path = Path.Combine(dir.FullName, "package.json"); + CreatePackageJson(path, PkgSimple); + CheckPackage(PackageJsonFactory.Create(new FilePackageJsonSource(path))); + } + + [TestMethod, Priority(0)] + public void TestReadFromDirectory() { + var dir = TempFileManager.GetNewTempDirectory(); + CreatePackageJson(Path.Combine(dir.FullName, "package.json"), PkgSimple); + CheckPackage(PackageJsonFactory.Create(new DirectoryPackageJsonSource(dir.FullName))); + } + + private static void CheckPackage(IPackageJson pkg) { + Assert.IsNotNull(pkg, "Package should not be null."); + Assert.AreEqual("TestPkg", pkg.Name, "Package name mismatch."); + Assert.AreEqual(SemverVersion.Parse("0.1.0"), pkg.Version, "Package version mismatch."); + } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/InstallUninstallPackageTests.cs b/Nodejs/Tests/NpmTests/InstallUninstallPackageTests.cs index 4da4f51a3..104298b17 100644 --- a/Nodejs/Tests/NpmTests/InstallUninstallPackageTests.cs +++ b/Nodejs/Tests/NpmTests/InstallUninstallPackageTests.cs @@ -1,180 +1,180 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.IO; -using System.Threading.Tasks; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - [TestClass] - public class InstallUninstallPackageTests : AbstractFilesystemPackageJsonTests { - [Ignore] - [TestMethod, Priority(0)] - public void TestAddPackageToSimplePackageJsonThenUninstall() { - var rootDir = CreateRootPackage(PkgSimple); - var controller = NpmControllerFactory.Create(rootDir, string.Empty); - controller.Refresh(); - var rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package with no dependencies should not be null."); - Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules before package install."); - - using (var commander = controller.CreateNpmCommander()) { - Task task = commander.InstallPackageByVersionAsync("sax", "*", DependencyType.Standard, true); - task.Wait(); - } - - Assert.AreNotEqual( - rootPackage, - controller.RootPackage, - "Root package should be different after package installed."); - - rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package should not be null after package installed."); - Assert.AreEqual(1, rootPackage.Modules.Count, "Should be one module after package installed."); - - var module = controller.RootPackage.Modules["sax"]; - Assert.IsNotNull(module, "Installed package should not be null."); - Assert.AreEqual("sax", module.Name, "Module name mismatch."); - Assert.IsNotNull(module.PackageJson, "Module package.json should not be null."); - Assert.IsTrue( - module.IsListedInParentPackageJson, - "Should be listed as a dependency in parent package.json."); - Assert.IsFalse(module.IsMissing, "Should not be marked as missing."); - Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); - Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); - Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); - Assert.IsTrue(module.HasPackageJson, "Module should have its own package.json"); - - using (var commander = controller.CreateNpmCommander()) { - Task task = commander.UninstallPackageAsync("sax"); - task.Wait(); - } - - Assert.AreNotEqual( - rootPackage, - controller.RootPackage, - "Root package should be different after package uninstalled."); - - rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package should not be null after package uninstalled."); - Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules after package installed."); - } - - [TestMethod, Priority(0)] - public void TestAddPackageNoPackageJsonThenUninstall() { - var rootDir = CreateRootPackage(PkgSimple); - File.Delete(Path.Combine(rootDir, "package.json")); - var controller = NpmControllerFactory.Create(rootDir, string.Empty); - controller.Refresh(); - var rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package with no dependencies should not be null."); - Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules before package install."); - - using (var commander = controller.CreateNpmCommander()) { - Task task = commander.InstallPackageByVersionAsync("sax", "*", DependencyType.Standard, true); - task.Wait(); - } - - Assert.AreNotEqual( - rootPackage, - controller.RootPackage, - "Root package should be different after package installed."); - - rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package should not be null after package installed."); - Assert.AreEqual(1, rootPackage.Modules.Count, "Should be one module after package installed."); - - var module = controller.RootPackage.Modules["sax"]; - Assert.IsNotNull(module, "Installed package should not be null."); - Assert.AreEqual("sax", module.Name, "Module name mismatch."); - Assert.IsNotNull(module.PackageJson, "Module package.json should not be null."); - Assert.IsFalse( - module.IsListedInParentPackageJson, - "Should be listed as a dependency in parent package.json."); - Assert.IsFalse(module.IsMissing, "Should not be marked as missing."); - Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); - Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); - Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); - Assert.IsTrue(module.HasPackageJson, "Module should have its own package.json"); - - using (var commander = controller.CreateNpmCommander()) { - Task task = commander.UninstallPackageAsync("sax"); - task.Wait(); - } - - Assert.AreNotEqual( - rootPackage, - controller.RootPackage, - "Root package should be different after package uninstalled."); - - rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package should not be null after package uninstalled."); - Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules after package installed."); - } - - [TestMethod, Priority(0)] - public void TestAddPackageNoSavePackageJsonThenUninstall() { - var rootDir = CreateRootPackage(PkgSimple); - var controller = NpmControllerFactory.Create(rootDir, string.Empty); - controller.Refresh(); - var rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package with no dependencies should not be null."); - Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules before package install."); - - using (var commander = controller.CreateNpmCommander()) { - Task task = commander.InstallPackageByVersionAsync("sax", "*", DependencyType.Standard, false); - task.Wait(); - } - - Assert.AreNotEqual( - rootPackage, - controller.RootPackage, - "Root package should be different after package installed."); - - rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package should not be null after package installed."); - Assert.AreEqual(1, rootPackage.Modules.Count, "Should be one module after package installed."); - - var module = controller.RootPackage.Modules["sax"]; - Assert.IsNotNull(module, "Installed package should not be null."); - Assert.AreEqual("sax", module.Name, "Module name mismatch."); - Assert.IsNotNull(module.PackageJson, "Module package.json should not be null."); - Assert.IsFalse( - module.IsListedInParentPackageJson, - "Should not be listed as a dependency in parent package.json."); - Assert.IsFalse(module.IsMissing, "Should not be marked as missing."); - Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); - Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); - Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); - Assert.IsTrue(module.HasPackageJson, "Module should have its own package.json"); - - using (var commander = controller.CreateNpmCommander()) { - Task task = commander.UninstallPackageAsync("sax"); - task.Wait(); - } - - Assert.AreNotEqual( - rootPackage, - controller.RootPackage, - "Root package should be different after package uninstalled."); - - rootPackage = controller.RootPackage; - Assert.IsNotNull(rootPackage, "Root package should not be null after package uninstalled."); - Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules after package installed."); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.IO; +using System.Threading.Tasks; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + [TestClass] + public class InstallUninstallPackageTests : AbstractFilesystemPackageJsonTests { + [Ignore] + [TestMethod, Priority(0)] + public void TestAddPackageToSimplePackageJsonThenUninstall() { + var rootDir = CreateRootPackage(PkgSimple); + var controller = NpmControllerFactory.Create(rootDir, string.Empty); + controller.Refresh(); + var rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package with no dependencies should not be null."); + Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules before package install."); + + using (var commander = controller.CreateNpmCommander()) { + Task task = commander.InstallPackageByVersionAsync("sax", "*", DependencyType.Standard, true); + task.Wait(); + } + + Assert.AreNotEqual( + rootPackage, + controller.RootPackage, + "Root package should be different after package installed."); + + rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package should not be null after package installed."); + Assert.AreEqual(1, rootPackage.Modules.Count, "Should be one module after package installed."); + + var module = controller.RootPackage.Modules["sax"]; + Assert.IsNotNull(module, "Installed package should not be null."); + Assert.AreEqual("sax", module.Name, "Module name mismatch."); + Assert.IsNotNull(module.PackageJson, "Module package.json should not be null."); + Assert.IsTrue( + module.IsListedInParentPackageJson, + "Should be listed as a dependency in parent package.json."); + Assert.IsFalse(module.IsMissing, "Should not be marked as missing."); + Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); + Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); + Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); + Assert.IsTrue(module.HasPackageJson, "Module should have its own package.json"); + + using (var commander = controller.CreateNpmCommander()) { + Task task = commander.UninstallPackageAsync("sax"); + task.Wait(); + } + + Assert.AreNotEqual( + rootPackage, + controller.RootPackage, + "Root package should be different after package uninstalled."); + + rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package should not be null after package uninstalled."); + Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules after package installed."); + } + + [TestMethod, Priority(0)] + public void TestAddPackageNoPackageJsonThenUninstall() { + var rootDir = CreateRootPackage(PkgSimple); + File.Delete(Path.Combine(rootDir, "package.json")); + var controller = NpmControllerFactory.Create(rootDir, string.Empty); + controller.Refresh(); + var rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package with no dependencies should not be null."); + Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules before package install."); + + using (var commander = controller.CreateNpmCommander()) { + Task task = commander.InstallPackageByVersionAsync("sax", "*", DependencyType.Standard, true); + task.Wait(); + } + + Assert.AreNotEqual( + rootPackage, + controller.RootPackage, + "Root package should be different after package installed."); + + rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package should not be null after package installed."); + Assert.AreEqual(1, rootPackage.Modules.Count, "Should be one module after package installed."); + + var module = controller.RootPackage.Modules["sax"]; + Assert.IsNotNull(module, "Installed package should not be null."); + Assert.AreEqual("sax", module.Name, "Module name mismatch."); + Assert.IsNotNull(module.PackageJson, "Module package.json should not be null."); + Assert.IsFalse( + module.IsListedInParentPackageJson, + "Should be listed as a dependency in parent package.json."); + Assert.IsFalse(module.IsMissing, "Should not be marked as missing."); + Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); + Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); + Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); + Assert.IsTrue(module.HasPackageJson, "Module should have its own package.json"); + + using (var commander = controller.CreateNpmCommander()) { + Task task = commander.UninstallPackageAsync("sax"); + task.Wait(); + } + + Assert.AreNotEqual( + rootPackage, + controller.RootPackage, + "Root package should be different after package uninstalled."); + + rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package should not be null after package uninstalled."); + Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules after package installed."); + } + + [TestMethod, Priority(0)] + public void TestAddPackageNoSavePackageJsonThenUninstall() { + var rootDir = CreateRootPackage(PkgSimple); + var controller = NpmControllerFactory.Create(rootDir, string.Empty); + controller.Refresh(); + var rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package with no dependencies should not be null."); + Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules before package install."); + + using (var commander = controller.CreateNpmCommander()) { + Task task = commander.InstallPackageByVersionAsync("sax", "*", DependencyType.Standard, false); + task.Wait(); + } + + Assert.AreNotEqual( + rootPackage, + controller.RootPackage, + "Root package should be different after package installed."); + + rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package should not be null after package installed."); + Assert.AreEqual(1, rootPackage.Modules.Count, "Should be one module after package installed."); + + var module = controller.RootPackage.Modules["sax"]; + Assert.IsNotNull(module, "Installed package should not be null."); + Assert.AreEqual("sax", module.Name, "Module name mismatch."); + Assert.IsNotNull(module.PackageJson, "Module package.json should not be null."); + Assert.IsFalse( + module.IsListedInParentPackageJson, + "Should not be listed as a dependency in parent package.json."); + Assert.IsFalse(module.IsMissing, "Should not be marked as missing."); + Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); + Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); + Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); + Assert.IsTrue(module.HasPackageJson, "Module should have its own package.json"); + + using (var commander = controller.CreateNpmCommander()) { + Task task = commander.UninstallPackageAsync("sax"); + task.Wait(); + } + + Assert.AreNotEqual( + rootPackage, + controller.RootPackage, + "Root package should be different after package uninstalled."); + + rootPackage = controller.RootPackage; + Assert.IsNotNull(rootPackage, "Root package should not be null after package uninstalled."); + Assert.AreEqual(0, rootPackage.Modules.Count, "Should be no modules after package installed."); + } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/MaxPathTests.cs b/Nodejs/Tests/NpmTests/MaxPathTests.cs index 73bb17c64..32ac23b03 100644 --- a/Nodejs/Tests/NpmTests/MaxPathTests.cs +++ b/Nodejs/Tests/NpmTests/MaxPathTests.cs @@ -1,71 +1,71 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Diagnostics; -using System.Threading; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - - [TestClass] - public class MaxPathTests : AbstractFilesystemPackageJsonTests { - - [TestMethod, Priority(0)] - public void TestAngularFullstackScaffoldedProject(){ - var rootDir = CreateRootPackageDir(); - var controller = NpmControllerFactory.Create(rootDir, string.Empty); - controller.OutputLogged += controller_OutputLogged; - controller.ErrorLogged += controller_OutputLogged; - controller.Refresh(); - - using (var commander = controller.CreateNpmCommander()) { - var task = commander.InstallGlobalPackageByVersionAsync("yo", "*"); - task.Wait(); - } - - var info = new ProcessStartInfo(); - - // TODO! - } - - [TestMethod, Priority(0)] - public void TestInstallUninstallMaxPathGlobalModule() { - var controller = NpmControllerFactory.Create(string.Empty, string.Empty); - - using (var commander = controller.CreateNpmCommander()) { - commander.InstallGlobalPackageByVersionAsync("yo", "^1.2.0").Wait(); - } - - Assert.IsNotNull(controller.GlobalPackages, "Cannot retrieve global packages after install"); - Assert.IsTrue(controller.GlobalPackages.Modules.Contains("yo"), "Global package failed to install"); - - using (var commander = controller.CreateNpmCommander()) { - commander.UninstallGlobalPackageAsync("yo").Wait(); - } - - // Command has completed, but need to wait for all files/folders to be deleted. - Thread.Sleep(5000); - - Assert.IsNotNull(controller.GlobalPackages, "Cannot retrieve global packages after uninstall"); - Assert.IsFalse(controller.GlobalPackages.Modules.Contains("yo"), "Global package failed to uninstall"); - } - - void controller_OutputLogged(object sender, NpmLogEventArgs e) { - Debug.WriteLine(e.LogText); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Diagnostics; +using System.Threading; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + + [TestClass] + public class MaxPathTests : AbstractFilesystemPackageJsonTests { + + [TestMethod, Priority(0)] + public void TestAngularFullstackScaffoldedProject(){ + var rootDir = CreateRootPackageDir(); + var controller = NpmControllerFactory.Create(rootDir, string.Empty); + controller.OutputLogged += controller_OutputLogged; + controller.ErrorLogged += controller_OutputLogged; + controller.Refresh(); + + using (var commander = controller.CreateNpmCommander()) { + var task = commander.InstallGlobalPackageByVersionAsync("yo", "*"); + task.Wait(); + } + + var info = new ProcessStartInfo(); + + // TODO! + } + + [TestMethod, Priority(0)] + public void TestInstallUninstallMaxPathGlobalModule() { + var controller = NpmControllerFactory.Create(string.Empty, string.Empty); + + using (var commander = controller.CreateNpmCommander()) { + commander.InstallGlobalPackageByVersionAsync("yo", "^1.2.0").Wait(); + } + + Assert.IsNotNull(controller.GlobalPackages, "Cannot retrieve global packages after install"); + Assert.IsTrue(controller.GlobalPackages.Modules.Contains("yo"), "Global package failed to install"); + + using (var commander = controller.CreateNpmCommander()) { + commander.UninstallGlobalPackageAsync("yo").Wait(); + } + + // Command has completed, but need to wait for all files/folders to be deleted. + Thread.Sleep(5000); + + Assert.IsNotNull(controller.GlobalPackages, "Cannot retrieve global packages after uninstall"); + Assert.IsFalse(controller.GlobalPackages.Modules.Contains("yo"), "Global package failed to uninstall"); + } + + void controller_OutputLogged(object sender, NpmLogEventArgs e) { + Debug.WriteLine(e.LogText); + } + } +} diff --git a/Nodejs/Tests/NpmTests/MockPackageCatalog.cs b/Nodejs/Tests/NpmTests/MockPackageCatalog.cs index d94f545e2..fce650910 100644 --- a/Nodejs/Tests/NpmTests/MockPackageCatalog.cs +++ b/Nodejs/Tests/NpmTests/MockPackageCatalog.cs @@ -1,55 +1,55 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.NodejsTools.Npm; - -namespace NpmTests { - public class MockPackageCatalog : IPackageCatalog { - private IDictionary _byName = new Dictionary(); - private IList _results; - - public MockPackageCatalog(IList results) { - _results = results; - LastRefreshed = DateTime.Now; - - foreach (var package in results) { - _byName[package.Name] = package; - } - } - - public DateTime LastRefreshed { get; private set; } - - public Task> GetCatalogPackagesAsync(string filterText, Uri registryUrl = null) { - return Task.FromResult(_results.AsEnumerable()); - } - - public IPackage this[string name] { - get { - IPackage match; - _byName.TryGetValue(name, out match); - return match; - } - } - - public long? ResultsCount { - get { return _results.LongCount(); } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.NodejsTools.Npm; + +namespace NpmTests { + public class MockPackageCatalog : IPackageCatalog { + private IDictionary _byName = new Dictionary(); + private IList _results; + + public MockPackageCatalog(IList results) { + _results = results; + LastRefreshed = DateTime.Now; + + foreach (var package in results) { + _byName[package.Name] = package; + } + } + + public DateTime LastRefreshed { get; private set; } + + public Task> GetCatalogPackagesAsync(string filterText, Uri registryUrl = null) { + return Task.FromResult(_results.AsEnumerable()); + } + + public IPackage this[string name] { + get { + IPackage match; + _byName.TryGetValue(name, out match); + return match; + } + } + + public long? ResultsCount { + get { return _results.LongCount(); } + } + } +} diff --git a/Nodejs/Tests/NpmTests/MockPackageJsonSource.cs b/Nodejs/Tests/NpmTests/MockPackageJsonSource.cs index 743a5b0a7..7498aeac9 100644 --- a/Nodejs/Tests/NpmTests/MockPackageJsonSource.cs +++ b/Nodejs/Tests/NpmTests/MockPackageJsonSource.cs @@ -1,28 +1,28 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Microsoft.NodejsTools.Npm; -using Newtonsoft.Json; - -namespace NpmTests { - internal class MockPackageJsonSource : IPackageJsonSource { - public MockPackageJsonSource(string packageJsonString) { - Package = JsonConvert.DeserializeObject(packageJsonString); - } - - public dynamic Package { get; private set; } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Microsoft.NodejsTools.Npm; +using Newtonsoft.Json; + +namespace NpmTests { + internal class MockPackageJsonSource : IPackageJsonSource { + public MockPackageJsonSource(string packageJsonString) { + Package = JsonConvert.DeserializeObject(packageJsonString); + } + + public dynamic Package { get; private set; } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/ModuleHierarchyTests.cs b/Nodejs/Tests/NpmTests/ModuleHierarchyTests.cs index ca4cfe28b..47d9ab883 100644 --- a/Nodejs/Tests/NpmTests/ModuleHierarchyTests.cs +++ b/Nodejs/Tests/NpmTests/ModuleHierarchyTests.cs @@ -1,226 +1,226 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Diagnostics; -using System.Linq; -using Microsoft.NodejsTools.Npm; -using Microsoft.NodejsTools.Npm.SPI; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - [TestClass] - public class ModuleHierarchyTests : AbstractFilesystemPackageJsonTests { - protected const string PkgSingleDependency = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""dependencies"": { - ""sax"": "">=0.1.0 <0.2.0"" - } -}"; - - protected const string PkgSingleRecursiveDependency = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""dependencies"": { - ""express"": ""4.0.0"" - } -}"; - - [TestMethod, Priority(0)] - public void TestReadRootPackageNoDependencies() { - var rootDir = CreateRootPackage(PkgSimple); - var pkg = RootPackageFactory.Create(rootDir); - Assert.IsNotNull(pkg, "Root package should not be null."); - Assert.AreEqual(rootDir, pkg.Path, "Package path mismatch."); - var json = pkg.PackageJson; - Assert.IsNotNull(json, "package.json should not be null."); - Assert.AreEqual(json.Name, pkg.Name, "Package name mismatch."); - Assert.AreEqual(json.Version, pkg.Version, "Package version mismatch."); - var modules = pkg.Modules; - Assert.IsNotNull(modules, "Modules should not be null."); - Assert.AreEqual(0, modules.Count, "Module count mismatch."); - } - - private static void RunNpmInstall(string rootDir) { - new NpmInstallCommand(rootDir).ExecuteAsync().GetAwaiter().GetResult(); - } - - [TestMethod, Priority(0)] - public void TestReadRootPackageOneDependency() { - var rootDir = CreateRootPackage(PkgSingleDependency); - RunNpmInstall(rootDir); - - var pkg = RootPackageFactory.Create(rootDir); - - var json = pkg.PackageJson; - var dependencies = json.AllDependencies; - Assert.AreEqual(1, dependencies.Count, "Dependency count mismatch."); - - IDependency dep = dependencies["sax"]; - Assert.IsNotNull(dep, "sax dependency should not be null."); - Assert.AreEqual(">=0.1.0 <0.2.0", dep.VersionRangeText, "Version range mismatch."); - - var modules = pkg.Modules; - Assert.AreEqual(1, modules.Count, "Module count mismatch"); - - IPackage module = modules[0]; - Assert.IsNotNull(module, "Module should not be null when retrieved by index."); - module = modules["sax"]; - Assert.IsNotNull(module, "Module should not be null when retrieved by name."); - - Assert.AreEqual(modules[0], modules["sax"], "Modules should be same whether retrieved by name or index."); - - Assert.AreEqual("sax", module.Name, "Module name mismatch."); - - // All of these should be indicated, in some way, in the Visual Studio treeview. - - Assert.IsNotNull(module.PackageJson, "Module package.json should not be null."); - - Assert.IsTrue( - module.IsListedInParentPackageJson, - "Should be listed as a dependency in parent package.json."); - Assert.IsFalse(module.IsMissing, "Should not be marked as missing."); - Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); - Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); - Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); - - // Redundant? - Assert.IsTrue(module.HasPackageJson, "Module should have its own package.json"); - } - - [TestMethod, Priority(0)] - public void TestReadRootPackageMissingDependency() { - var rootDir = CreateRootPackage(PkgSingleDependency); - - var pkg = RootPackageFactory.Create(rootDir); - - var json = pkg.PackageJson; - var dependencies = json.AllDependencies; - Assert.AreEqual(1, dependencies.Count, "Dependency count mismatch."); - - IDependency dep = dependencies["sax"]; - Assert.IsNotNull(dep, "sax dependency should not be null."); - Assert.AreEqual(">=0.1.0 <0.2.0", dep.VersionRangeText, "Version range mismatch."); - - var modules = pkg.Modules; - Assert.AreEqual(1, modules.Count, "Module count mismatch"); - - IPackage module = modules[0]; - Assert.IsNotNull(module, "Module should not be null when retrieved by index."); - module = modules["sax"]; - Assert.IsNotNull(module, "Module should not be null when retrieved by name."); - - Assert.AreEqual(modules[0], modules["sax"], "Modules should be same whether retrieved by name or index."); - - Assert.AreEqual("sax", module.Name, "Module name mismatch."); - - // All of these should be indicated, in some way, in the Visual Studio treeview. - - Assert.IsNull(module.PackageJson, "Module package.json should be null for missing dependency."); - - Assert.IsTrue( - module.IsListedInParentPackageJson, - "Should be listed as a dependency in parent package.json."); - Assert.IsTrue(module.IsMissing, "Should be marked as missing."); - Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); - Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); - Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); - - // Redundant? - Assert.IsFalse(module.HasPackageJson, "Missing module should not have its own package.json"); - } - - [TestMethod, Priority(0)] - public void TestReadRootDependencyRecursive() { - var rootDir = CreateRootPackage(PkgSingleRecursiveDependency); - RunNpmInstall(rootDir); - - var pkg = RootPackageFactory.Create(rootDir); - - var json = pkg.PackageJson; - var dependencies = json.AllDependencies; - Assert.AreEqual(1, dependencies.Count, "Dependency count mismatch."); - - IDependency dep = dependencies["express"]; - Assert.IsNotNull(dep, "express dependency should not be null."); - Assert.AreEqual("4.0.0", dep.VersionRangeText, "Version range mismatch."); - - var modules = pkg.Modules; - Assert.AreEqual(1, modules.Count, "Module count mismatch"); - - IPackage module = modules[0]; - Assert.IsNotNull(module, "Module should not be null when retrieved by index."); - module = modules["express"]; - Assert.IsNotNull(module, "Module should not be null when retrieved by name."); - - Assert.AreEqual( - modules[0], - modules["express"], - "Modules should be same whether retrieved by name or index."); - - Assert.AreEqual("express", module.Name, "Module name mismatch."); - - Assert.AreEqual("4.0.0", module.Version.ToString(), "Module version mismatch"); - - var expectedModules = new string[] { - "accepts", - "buffer-crc32", - "cookie", - "cookie-signature", - "debug", - "escape-html", - "fresh", - "merge-descriptors", - "methods", - "parseurl", - "path-to-regexp", - "qs", - "range-parser", - "send", - "serve-static", - "type-is", - "utils-merge" - }; - - modules = module.Modules; - - Console.WriteLine("module.Modules includes: {0}", string.Join(", ", modules.Select(m => m.Name))); - - Assert.AreEqual(module.PackageJson.Dependencies.Count, modules.Count, "Sub-module count mismatch."); - foreach (var name in expectedModules) { - Console.WriteLine("Expecting {0}", name); - var current = modules[name]; - Assert.IsNotNull(current, "Module should not be null when retrieved by name."); - - Assert.AreEqual(name, current.Name, "Module name mismatch."); - - Assert.IsNotNull(current.PackageJson, "Module package.json should not be null."); - - Assert.IsTrue( - current.IsListedInParentPackageJson, - "Should be listed as a dependency in parent package.json."); - Assert.IsFalse(current.IsMissing, "Should not be marked as missing."); - Assert.IsFalse(current.IsDevDependency, "Should not be marked as dev dependency."); - Assert.IsFalse(current.IsOptionalDependency, "Should not be marked as optional dependency."); - Assert.IsFalse(current.IsBundledDependency, "Should not be marked as bundled dependency."); - - // Redundant? - Assert.IsTrue(current.HasPackageJson, "Module should have its own package.json"); - } - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Diagnostics; +using System.Linq; +using Microsoft.NodejsTools.Npm; +using Microsoft.NodejsTools.Npm.SPI; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + [TestClass] + public class ModuleHierarchyTests : AbstractFilesystemPackageJsonTests { + protected const string PkgSingleDependency = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""dependencies"": { + ""sax"": "">=0.1.0 <0.2.0"" + } +}"; + + protected const string PkgSingleRecursiveDependency = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""dependencies"": { + ""express"": ""4.0.0"" + } +}"; + + [TestMethod, Priority(0)] + public void TestReadRootPackageNoDependencies() { + var rootDir = CreateRootPackage(PkgSimple); + var pkg = RootPackageFactory.Create(rootDir); + Assert.IsNotNull(pkg, "Root package should not be null."); + Assert.AreEqual(rootDir, pkg.Path, "Package path mismatch."); + var json = pkg.PackageJson; + Assert.IsNotNull(json, "package.json should not be null."); + Assert.AreEqual(json.Name, pkg.Name, "Package name mismatch."); + Assert.AreEqual(json.Version, pkg.Version, "Package version mismatch."); + var modules = pkg.Modules; + Assert.IsNotNull(modules, "Modules should not be null."); + Assert.AreEqual(0, modules.Count, "Module count mismatch."); + } + + private static void RunNpmInstall(string rootDir) { + new NpmInstallCommand(rootDir).ExecuteAsync().GetAwaiter().GetResult(); + } + + [TestMethod, Priority(0)] + public void TestReadRootPackageOneDependency() { + var rootDir = CreateRootPackage(PkgSingleDependency); + RunNpmInstall(rootDir); + + var pkg = RootPackageFactory.Create(rootDir); + + var json = pkg.PackageJson; + var dependencies = json.AllDependencies; + Assert.AreEqual(1, dependencies.Count, "Dependency count mismatch."); + + IDependency dep = dependencies["sax"]; + Assert.IsNotNull(dep, "sax dependency should not be null."); + Assert.AreEqual(">=0.1.0 <0.2.0", dep.VersionRangeText, "Version range mismatch."); + + var modules = pkg.Modules; + Assert.AreEqual(1, modules.Count, "Module count mismatch"); + + IPackage module = modules[0]; + Assert.IsNotNull(module, "Module should not be null when retrieved by index."); + module = modules["sax"]; + Assert.IsNotNull(module, "Module should not be null when retrieved by name."); + + Assert.AreEqual(modules[0], modules["sax"], "Modules should be same whether retrieved by name or index."); + + Assert.AreEqual("sax", module.Name, "Module name mismatch."); + + // All of these should be indicated, in some way, in the Visual Studio treeview. + + Assert.IsNotNull(module.PackageJson, "Module package.json should not be null."); + + Assert.IsTrue( + module.IsListedInParentPackageJson, + "Should be listed as a dependency in parent package.json."); + Assert.IsFalse(module.IsMissing, "Should not be marked as missing."); + Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); + Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); + Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); + + // Redundant? + Assert.IsTrue(module.HasPackageJson, "Module should have its own package.json"); + } + + [TestMethod, Priority(0)] + public void TestReadRootPackageMissingDependency() { + var rootDir = CreateRootPackage(PkgSingleDependency); + + var pkg = RootPackageFactory.Create(rootDir); + + var json = pkg.PackageJson; + var dependencies = json.AllDependencies; + Assert.AreEqual(1, dependencies.Count, "Dependency count mismatch."); + + IDependency dep = dependencies["sax"]; + Assert.IsNotNull(dep, "sax dependency should not be null."); + Assert.AreEqual(">=0.1.0 <0.2.0", dep.VersionRangeText, "Version range mismatch."); + + var modules = pkg.Modules; + Assert.AreEqual(1, modules.Count, "Module count mismatch"); + + IPackage module = modules[0]; + Assert.IsNotNull(module, "Module should not be null when retrieved by index."); + module = modules["sax"]; + Assert.IsNotNull(module, "Module should not be null when retrieved by name."); + + Assert.AreEqual(modules[0], modules["sax"], "Modules should be same whether retrieved by name or index."); + + Assert.AreEqual("sax", module.Name, "Module name mismatch."); + + // All of these should be indicated, in some way, in the Visual Studio treeview. + + Assert.IsNull(module.PackageJson, "Module package.json should be null for missing dependency."); + + Assert.IsTrue( + module.IsListedInParentPackageJson, + "Should be listed as a dependency in parent package.json."); + Assert.IsTrue(module.IsMissing, "Should be marked as missing."); + Assert.IsFalse(module.IsDevDependency, "Should not be marked as dev dependency."); + Assert.IsFalse(module.IsOptionalDependency, "Should not be marked as optional dependency."); + Assert.IsFalse(module.IsBundledDependency, "Should not be marked as bundled dependency."); + + // Redundant? + Assert.IsFalse(module.HasPackageJson, "Missing module should not have its own package.json"); + } + + [TestMethod, Priority(0)] + public void TestReadRootDependencyRecursive() { + var rootDir = CreateRootPackage(PkgSingleRecursiveDependency); + RunNpmInstall(rootDir); + + var pkg = RootPackageFactory.Create(rootDir); + + var json = pkg.PackageJson; + var dependencies = json.AllDependencies; + Assert.AreEqual(1, dependencies.Count, "Dependency count mismatch."); + + IDependency dep = dependencies["express"]; + Assert.IsNotNull(dep, "express dependency should not be null."); + Assert.AreEqual("4.0.0", dep.VersionRangeText, "Version range mismatch."); + + var modules = pkg.Modules; + Assert.AreEqual(1, modules.Count, "Module count mismatch"); + + IPackage module = modules[0]; + Assert.IsNotNull(module, "Module should not be null when retrieved by index."); + module = modules["express"]; + Assert.IsNotNull(module, "Module should not be null when retrieved by name."); + + Assert.AreEqual( + modules[0], + modules["express"], + "Modules should be same whether retrieved by name or index."); + + Assert.AreEqual("express", module.Name, "Module name mismatch."); + + Assert.AreEqual("4.0.0", module.Version.ToString(), "Module version mismatch"); + + var expectedModules = new string[] { + "accepts", + "buffer-crc32", + "cookie", + "cookie-signature", + "debug", + "escape-html", + "fresh", + "merge-descriptors", + "methods", + "parseurl", + "path-to-regexp", + "qs", + "range-parser", + "send", + "serve-static", + "type-is", + "utils-merge" + }; + + modules = module.Modules; + + Console.WriteLine("module.Modules includes: {0}", string.Join(", ", modules.Select(m => m.Name))); + + Assert.AreEqual(module.PackageJson.Dependencies.Count, modules.Count, "Sub-module count mismatch."); + foreach (var name in expectedModules) { + Console.WriteLine("Expecting {0}", name); + var current = modules[name]; + Assert.IsNotNull(current, "Module should not be null when retrieved by name."); + + Assert.AreEqual(name, current.Name, "Module name mismatch."); + + Assert.IsNotNull(current.PackageJson, "Module package.json should not be null."); + + Assert.IsTrue( + current.IsListedInParentPackageJson, + "Should be listed as a dependency in parent package.json."); + Assert.IsFalse(current.IsMissing, "Should not be marked as missing."); + Assert.IsFalse(current.IsDevDependency, "Should not be marked as dev dependency."); + Assert.IsFalse(current.IsOptionalDependency, "Should not be marked as optional dependency."); + Assert.IsFalse(current.IsBundledDependency, "Should not be marked as bundled dependency."); + + // Redundant? + Assert.IsTrue(current.HasPackageJson, "Module should have its own package.json"); + } + } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/NpmSearchTests.cs b/Nodejs/Tests/NpmTests/NpmSearchTests.cs index 4ca735ed3..fd71b1a67 100644 --- a/Nodejs/Tests/NpmTests/NpmSearchTests.cs +++ b/Nodejs/Tests/NpmTests/NpmSearchTests.cs @@ -1,565 +1,565 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using Microsoft.NodejsTools.Npm; -using Microsoft.NodejsTools.Npm.SPI; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using TestUtilities; - -namespace NpmTests { - - [TestClass] - [DeploymentItem(@"TestData\NpmSearchData\", "NpmSearchData")] - [DeploymentItem(@"sqlite3.dll")] - public class NpmSearchTests { - - private const string PackageCacheAllJsonFilename = "packagecache.min.json"; - private const string PackageCacheSinceJsonFilename = "since_packages.object.json"; - private const string PackageCacheSinceJsonArrayFilename = "since_packages.array.json"; - private const string PackageCacheDirectory = @"NpmSearchData\NpmCache"; - private const string PackageCacheFilename = @"NpmSearchData\testpackagecache.sqlite"; - private const string RegistryUrl = "http://registry.npmjs.org"; - - [ClassInitialize] - public static void Init(TestContext context) { - AssertListener.Initialize(); - } - - private TextReader GetCatalogueReader(string filename) { - return new StreamReader(string.Format(@"NpmSearchData\{0}", filename)); - } - - private void CheckPackage( - IPackage package, - string expectedName, - string expectedDescription, - string expectedAuthor, - string expectedPublishDateTime, - SemverVersion expectedVersion, - IEnumerable expectedVersions, - IEnumerable expectedKeywords) { - - Assert.AreEqual(expectedName, package.Name, "Invalid name."); - Assert.AreEqual(expectedDescription, package.Description, "Invalid description."); - string actualAuthorString = null == package.Author ? null : package.Author.Name; - Assert.AreEqual(expectedAuthor, actualAuthorString, "Invalid author."); - Assert.AreEqual(expectedPublishDateTime, package.PublishDateTimeString, "Invalid publish date/time."); - Assert.AreEqual(expectedVersion, package.Version, "Invalid version."); - - // Sometimes authors include duplicate keywords in the list - AssertUtil.ArrayEquals(package.Keywords.Distinct().ToList(), expectedKeywords.Distinct().ToList()); - AssertUtil.ArrayEquals(package.AvailableVersions.ToList(), expectedVersions.ToList()); - } - - private void CheckPackage( - IList packages, - IDictionary packagesByName, - int expectedIndex, - string expectedName, - string expectedDescription, - string expectedAuthor, - string expectedPublishDateTime, - SemverVersion expectedVersion, - IEnumerable expectedVersions, - IEnumerable expectedKeywords) { - CheckPackage( - packagesByName[expectedName], - expectedName, - expectedDescription, - expectedAuthor, - expectedPublishDateTime, - expectedVersion, - expectedVersions, - expectedKeywords); - - if (expectedIndex >= 0) { - for (int index = 0, size = packages.Count; index < size; ++index ) { - if (packages[index].Name == expectedName) { - Assert.AreEqual( - expectedIndex, - index, - string.Format("Package '{0}' not at expected index in list.", expectedName)); - } - } - - CheckPackage( - packages[expectedIndex], - expectedName, - expectedDescription, - expectedAuthor, - expectedPublishDateTime, - expectedVersion, - expectedVersions, - expectedKeywords); - } - } - - private IList GetTestPackageList( - string cachePath, - out IDictionary byName) { - IList target = new List(); - - target = new NpmGetCatalogCommand(string.Empty, cachePath, false, RegistryUrl).GetCatalogPackagesAsync(string.Empty, new Uri(RegistryUrl)).GetAwaiter().GetResult().ToList(); - - // Do this after because package names can be split across multiple - // lines and therefore may change after the IPackage is initially created. - IDictionary temp = new Dictionary(); - foreach (var package in target ) { - temp[package.Name] = package; - } - - byName = temp; - return target; - } - - private IPackageCatalog GetTestPackageCatalog(string filename) { - IDictionary byName; - return new MockPackageCatalog(GetTestPackageList(filename, out byName)); - } - - [TestMethod, Priority(0)] - public void CheckDatabaseCreation() { - string databaseFilename = NpmGetCatalogCommand.DatabaseCacheFilename; - string registryFilename = NpmGetCatalogCommand.RegistryCacheFilename; - string cachePath = "CachePath"; - string registryDirectory = "registry"; - - Uri registryUrl = new Uri(RegistryUrl); - - string catalogDatabaseFilename = Path.Combine(cachePath, databaseFilename); - string registryDatabaseFilename = Path.Combine(cachePath, registryDirectory, registryFilename); - string relativeRegistryDatabaseFilename = Path.Combine(registryDirectory, registryFilename); - - - using (var reader = GetCatalogueReader(PackageCacheAllJsonFilename)) { - var getCatalogCommand = new NpmGetCatalogCommand(string.Empty, cachePath, false, RegistryUrl); - getCatalogCommand.CreateCatalogDatabaseAndInsertEntries(catalogDatabaseFilename, registryUrl, registryDirectory); - new NpmGetCatalogCommand(string.Empty, cachePath, false).ParseResultsAndAddToDatabase(reader, registryDatabaseFilename, registryUrl.ToString()); - } - - IDictionary byName; - var target = GetTestPackageList(cachePath, out byName); - - Assert.AreEqual(102, target.Count); - - CheckPackage( - target, - byName, - 93, - "cordova", - "Cordova command line interface tool", - "Anis Kadri", - "07/08/2014 17:55:34", - SemverVersion.Parse("3.5.0-0.2.6"), - new[] { - SemverVersion.Parse("3.5.0-0.2.6"), - SemverVersion.Parse("3.5.0-0.2.4") - }, - new[] { "cordova", "client", "cli" } - ); - } - - [TestMethod, Priority(0)] - public void CheckDatabaseUpdate() { - string cachePath = "NpmCacheUpdate"; - string registryPath = Path.Combine(cachePath, "registry", NpmGetCatalogCommand.RegistryCacheFilename); - - FileUtils.CopyDirectory(PackageCacheDirectory, cachePath); - - using (var reader = GetCatalogueReader(PackageCacheSinceJsonFilename)) { - new NpmGetCatalogCommand(string.Empty, cachePath, false).ParseResultsAndAddToDatabase(reader, registryPath, RegistryUrl); - } - - IDictionary byName; - var target = GetTestPackageList(cachePath, out byName); - - Assert.AreEqual(89978, target.Count); - - // Package updated successfully - CheckPackage( - target, - byName, - 13898, - "cordova", - "Cordova command line interface tool", - "Anis Kadri", - "10/16/2014 18:05:13", - SemverVersion.Parse("4.0.0"), - new[] { - SemverVersion.Parse("4.0.0"), - SemverVersion.Parse("3.6.0-0.2.8"), - }, - new[] { "cordova", "client", "cli" } - ); - - // Package added successfully - CheckPackage( - target, - byName, - 54151, - "mytestpackage98", - null, - null, - "08/14/2014 19:46:24", - SemverVersion.Parse("0.1.3"), - new[] { - SemverVersion.Parse("0.1.3") - }, - Enumerable.Empty() - ); - - } - - [TestMethod, Priority(0)] - public void CheckDatabaseUpdateArray() { - string cachePath = "NpmCacheUpdate"; - string registryPath = Path.Combine(cachePath, "registry", NpmGetCatalogCommand.RegistryCacheFilename); - - FileUtils.CopyDirectory(PackageCacheDirectory, cachePath); - - using (var reader = GetCatalogueReader(PackageCacheSinceJsonArrayFilename)) { - new NpmGetCatalogCommand(string.Empty, cachePath, false).ParseResultsAndAddToDatabase(reader, registryPath, RegistryUrl); - } - - IDictionary byName; - var target = GetTestPackageList(cachePath, out byName); - - Assert.AreEqual(90066, target.Count); - - // Package updated successfully - CheckPackage( - target, - byName, - 13986, - "cordova", - "Cordova command line interface tool", - "Anis Kadri", - "10/16/2014 18:05:13", - SemverVersion.Parse("4.0.0"), - new[] { - SemverVersion.Parse("4.0.0"), - SemverVersion.Parse("3.6.0-0.2.8"), - }, - new[] { "cordova", "client", "cli" } - ); - - // Package added successfully - CheckPackage( - target, - byName, - 253, - "9e-sass-lint", - "Makes sure you stick to our CSS rule order http://9elements.com/css-rule-order/", - "Sascha Gehlich", - "08/19/2015 08:21:25", - SemverVersion.Parse("0.0.12"), - new[] { - SemverVersion.Parse("0.0.12") - }, - new[] { "sass", "css", "lint", "rules" } - ); - - } - - [TestMethod, Priority(0)] - public void CheckPackageWithBuildPreReleaseInfo() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - CheckPackage( - target, - byName, - 65900, - "psc-cms-js", - "js library for Psc CMS (pscheit/psc-cms). shim reposistory for builds.", - "Philipp Scheit", - "01/07/2014 10:57:58", - SemverVersion.Parse("1.3.0-517056d"), - new[] { - SemverVersion.Parse("1.3.0-95847e2"), - SemverVersion.Parse("1.3.0-517056d"), - SemverVersion.Parse("1.4.0-e14fdf0") - }, - new[] { "cms", "framework" }); - } - - - private void CheckSensibleNumberOfNonZeroVersions(ICollection target) { - int sensibleVersionCount = 0; - var zero = SemverVersion.Parse("0.0.0"); - foreach (var package in target) { - if (package.Version != zero) { - ++sensibleVersionCount; - } - } - - // Let's say (it'll be much higher) but at least 25% of packages must have a sensible version number - Assert.IsTrue( - sensibleVersionCount > target.Count / 4, - string.Format("There are only {0} packages with version numbers other than {1}", sensibleVersionCount, zero)); - } - - private void CheckOnlyOneOfEachPackage(IEnumerable target) { - var packageCounts = new Dictionary>(); - foreach (IPackage package in target) { - if (!packageCounts.ContainsKey(package.Name)) { - packageCounts[package.Name] = new List(); - } - packageCounts[package.Name].Add(package); - } - - var moreThanOne = new List>(); - foreach (string name in packageCounts.Keys) { - if (packageCounts[name].Count > 1) { - moreThanOne.Add(packageCounts[name]); - } - } - - if (moreThanOne.Count > 0) { - var buff = new StringBuilder(); - foreach (var list in moreThanOne) { - if (buff.Length > 0) { - buff.Append(", "); - } - buff.Append("["); - foreach (var package in list) { - if (buff[buff.Length - 1] != '[') { - buff.Append(", "); - } - buff.Append("{\""); - buff.Append(package.Name); - buff.Append("\", \""); - buff.Append(package.Version); - buff.Append("\", \""); - buff.Append(package.Description); - buff.Append("\", \""); - buff.Append(package.Author); - buff.Append("\", \""); - buff.Append(string.Join(" ", package.Keywords)); - buff.Append("\"}"); - } - buff.Append("]"); - } - Assert.Fail(string.Format("Multiple package instances found: {0}", buff.ToString())); - } - } - - [TestMethod, Priority(0)] - public void CheckNonZeroPackageVersionsExist() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - CheckSensibleNumberOfNonZeroVersions(target); - } - - [TestMethod, Priority(0)] - public void CheckCorrectPackageCount() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - Assert.AreEqual(89924, target.Count, "Unexpected package count in catalogue list."); - } - - [TestMethod, Priority(0)] - public void CheckNoDuplicatePackages() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - CheckOnlyOneOfEachPackage(target); - } - - [TestMethod, Priority(0)] - public void CheckListAndDictByNameSameSize() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - Assert.AreEqual(target.Count, byName.Count, "Number of packages should be same in list and dictionary."); - } - - [TestMethod, Priority(0)] - public void CheckFirstPackageInCatalog() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - CheckPackage( - target, - byName, - 0, - "0", - null, - null, - "06/17/2014 06:38:43", - new SemverVersion(0, 0, 0), - new[] { - new SemverVersion(0, 0, 0) - }, - Enumerable.Empty()); - } - - [TestMethod, Priority(0)] - public void CheckLastPackageInCatalog_zzz() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - CheckPackage( - target, - byName, - 89923, - "z_test", - "zhanghao test", - "zhanghao", - "01/02/2014 03:28:23", - new SemverVersion(1, 0, 0), - new[] { - new SemverVersion(1, 0, 0) - }, - Enumerable.Empty()); - } - - [TestMethod, Priority(0)] - public void CheckPackageEqualsInDescription() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - CheckPackage( - target, - byName, - 62060, - "particularizable", - "particularizable ================ `enumerable` was taken.", - "ELLIOTTCABLE", - "06/11/2013 22:48:35", - SemverVersion.Parse("1.0.0"), - new[] { - SemverVersion.Parse("1.0.0") - }, - Enumerable.Empty()); - } - - [TestMethod, Priority(0)] - public void CheckPackageNoDescriptionAuthorVersion() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - - CheckPackage( - target, - byName, - 25, - "122", - null, - null, - "03/03/2014 16:52:16", - SemverVersion.UnknownVersion, - Enumerable.Empty(), - Enumerable.Empty()); - } - - [TestMethod, Priority(0)] - public void CheckPackageNoDescription() { - IDictionary byName; - var target = GetTestPackageList(PackageCacheDirectory, out byName); - - CheckPackage( - target, - byName, - 455, - "active-client", - null, - "Subbu Allamaraju", - "01/03/2011 21:21:12", - SemverVersion.Parse("0.1.1"), - new[] { - SemverVersion.Parse("0.1.1") - }, - Enumerable.Empty()); - } - - private IList GetFilteredPackageList(string filterString) { - var filter = new DatabasePackageCatalogFilter(PackageCacheFilename); - return filter.Filter(filterString).ToList(); - } - - [TestMethod, Priority(0)] - public void TestFilterString() { - const string filterString = "express"; - var results = GetFilteredPackageList(filterString); - Assert.IsTrue(results.Count > 0, string.Format("Should be some filter results for '{0}'.", filterString)); - foreach (var package in results) { - bool match = false; - if (package.Name.ToLower().Contains(filterString)) { - match = true; - } else if (null != package.Description && package.Description.ToLower().Contains(filterString)) { - match = true; - } else { - if (package.Keywords.Any(keyword => keyword.ToLower().Contains(filterString))) { - match = true; - } - } - - Assert.IsTrue(match, string.Format("Found no match for filter string '{0}' in package '{1}'.", filterString, package.Name)); - } - } - - [Ignore] - [TestMethod, Priority(0)] - public void TestFilterStringWithHyphens() { - const string - filterStringWithHyphenMiddle = "grunt-contrib", - filterStringWithHyphenSuffix = "amazing-", - filterStringWithHyphenPrefix = "-grunt"; - - var results = GetFilteredPackageList(filterStringWithHyphenMiddle); - Assert.AreEqual(filterStringWithHyphenMiddle, results.First().Name, "Exact filter string match should be first in list."); - - results = GetFilteredPackageList(filterStringWithHyphenSuffix); - Assert.AreEqual(filterStringWithHyphenSuffix, results.First().Name, "Exact filter string match (including suffix) should be first."); - - // Package names cannot begin with a hyphen, but we would expect the first result to at least include the filter string. - results = GetFilteredPackageList(filterStringWithHyphenPrefix); - Assert.IsTrue(results.First().Name.Contains(filterStringWithHyphenPrefix), "Filter string match (including prefix) should be first"); - } - - private void CheckRegexFilterResults(string filterString, IList results) { - const string expectedMatch = "express"; - Assert.IsTrue(results.Count > 0, string.Format("Should be some filter results for '{0}'.", filterString)); - foreach (var package in results) { - bool match = false; - if (package.Name.ToLower() == expectedMatch) { - match = true; - } else if (null != package.Description && package.Description.ToLower() == expectedMatch) { - match = true; - } else { - if (package.Keywords.Any(keyword => keyword.ToLower() == expectedMatch)) { - match = true; - } - } - - Assert.IsTrue(match, string.Format("Found no match for filter regex '{0}' in package '{1}'.", filterString, package.Name)); - } - } - - private void TestFilterRegex(string filterString) { - CheckRegexFilterResults(filterString, GetFilteredPackageList(filterString)); - } - - [TestMethod, Priority(0)] - public void TestFilterRegex() { - TestFilterRegex("/^express$"); - } - - [TestMethod, Priority(0)] - public void TestFilterRegexTrailingSlash() { - TestFilterRegex("/^express$/"); - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Microsoft.NodejsTools.Npm; +using Microsoft.NodejsTools.Npm.SPI; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using TestUtilities; + +namespace NpmTests { + + [TestClass] + [DeploymentItem(@"TestData\NpmSearchData\", "NpmSearchData")] + [DeploymentItem(@"sqlite3.dll")] + public class NpmSearchTests { + + private const string PackageCacheAllJsonFilename = "packagecache.min.json"; + private const string PackageCacheSinceJsonFilename = "since_packages.object.json"; + private const string PackageCacheSinceJsonArrayFilename = "since_packages.array.json"; + private const string PackageCacheDirectory = @"NpmSearchData\NpmCache"; + private const string PackageCacheFilename = @"NpmSearchData\testpackagecache.sqlite"; + private const string RegistryUrl = "http://registry.npmjs.org"; + + [ClassInitialize] + public static void Init(TestContext context) { + AssertListener.Initialize(); + } + + private TextReader GetCatalogueReader(string filename) { + return new StreamReader(string.Format(@"NpmSearchData\{0}", filename)); + } + + private void CheckPackage( + IPackage package, + string expectedName, + string expectedDescription, + string expectedAuthor, + string expectedPublishDateTime, + SemverVersion expectedVersion, + IEnumerable expectedVersions, + IEnumerable expectedKeywords) { + + Assert.AreEqual(expectedName, package.Name, "Invalid name."); + Assert.AreEqual(expectedDescription, package.Description, "Invalid description."); + string actualAuthorString = null == package.Author ? null : package.Author.Name; + Assert.AreEqual(expectedAuthor, actualAuthorString, "Invalid author."); + Assert.AreEqual(expectedPublishDateTime, package.PublishDateTimeString, "Invalid publish date/time."); + Assert.AreEqual(expectedVersion, package.Version, "Invalid version."); + + // Sometimes authors include duplicate keywords in the list + AssertUtil.ArrayEquals(package.Keywords.Distinct().ToList(), expectedKeywords.Distinct().ToList()); + AssertUtil.ArrayEquals(package.AvailableVersions.ToList(), expectedVersions.ToList()); + } + + private void CheckPackage( + IList packages, + IDictionary packagesByName, + int expectedIndex, + string expectedName, + string expectedDescription, + string expectedAuthor, + string expectedPublishDateTime, + SemverVersion expectedVersion, + IEnumerable expectedVersions, + IEnumerable expectedKeywords) { + CheckPackage( + packagesByName[expectedName], + expectedName, + expectedDescription, + expectedAuthor, + expectedPublishDateTime, + expectedVersion, + expectedVersions, + expectedKeywords); + + if (expectedIndex >= 0) { + for (int index = 0, size = packages.Count; index < size; ++index ) { + if (packages[index].Name == expectedName) { + Assert.AreEqual( + expectedIndex, + index, + string.Format("Package '{0}' not at expected index in list.", expectedName)); + } + } + + CheckPackage( + packages[expectedIndex], + expectedName, + expectedDescription, + expectedAuthor, + expectedPublishDateTime, + expectedVersion, + expectedVersions, + expectedKeywords); + } + } + + private IList GetTestPackageList( + string cachePath, + out IDictionary byName) { + IList target = new List(); + + target = new NpmGetCatalogCommand(string.Empty, cachePath, false, RegistryUrl).GetCatalogPackagesAsync(string.Empty, new Uri(RegistryUrl)).GetAwaiter().GetResult().ToList(); + + // Do this after because package names can be split across multiple + // lines and therefore may change after the IPackage is initially created. + IDictionary temp = new Dictionary(); + foreach (var package in target ) { + temp[package.Name] = package; + } + + byName = temp; + return target; + } + + private IPackageCatalog GetTestPackageCatalog(string filename) { + IDictionary byName; + return new MockPackageCatalog(GetTestPackageList(filename, out byName)); + } + + [TestMethod, Priority(0)] + public void CheckDatabaseCreation() { + string databaseFilename = NpmGetCatalogCommand.DatabaseCacheFilename; + string registryFilename = NpmGetCatalogCommand.RegistryCacheFilename; + string cachePath = "CachePath"; + string registryDirectory = "registry"; + + Uri registryUrl = new Uri(RegistryUrl); + + string catalogDatabaseFilename = Path.Combine(cachePath, databaseFilename); + string registryDatabaseFilename = Path.Combine(cachePath, registryDirectory, registryFilename); + string relativeRegistryDatabaseFilename = Path.Combine(registryDirectory, registryFilename); + + + using (var reader = GetCatalogueReader(PackageCacheAllJsonFilename)) { + var getCatalogCommand = new NpmGetCatalogCommand(string.Empty, cachePath, false, RegistryUrl); + getCatalogCommand.CreateCatalogDatabaseAndInsertEntries(catalogDatabaseFilename, registryUrl, registryDirectory); + new NpmGetCatalogCommand(string.Empty, cachePath, false).ParseResultsAndAddToDatabase(reader, registryDatabaseFilename, registryUrl.ToString()); + } + + IDictionary byName; + var target = GetTestPackageList(cachePath, out byName); + + Assert.AreEqual(102, target.Count); + + CheckPackage( + target, + byName, + 93, + "cordova", + "Cordova command line interface tool", + "Anis Kadri", + "07/08/2014 17:55:34", + SemverVersion.Parse("3.5.0-0.2.6"), + new[] { + SemverVersion.Parse("3.5.0-0.2.6"), + SemverVersion.Parse("3.5.0-0.2.4") + }, + new[] { "cordova", "client", "cli" } + ); + } + + [TestMethod, Priority(0)] + public void CheckDatabaseUpdate() { + string cachePath = "NpmCacheUpdate"; + string registryPath = Path.Combine(cachePath, "registry", NpmGetCatalogCommand.RegistryCacheFilename); + + FileUtils.CopyDirectory(PackageCacheDirectory, cachePath); + + using (var reader = GetCatalogueReader(PackageCacheSinceJsonFilename)) { + new NpmGetCatalogCommand(string.Empty, cachePath, false).ParseResultsAndAddToDatabase(reader, registryPath, RegistryUrl); + } + + IDictionary byName; + var target = GetTestPackageList(cachePath, out byName); + + Assert.AreEqual(89978, target.Count); + + // Package updated successfully + CheckPackage( + target, + byName, + 13898, + "cordova", + "Cordova command line interface tool", + "Anis Kadri", + "10/16/2014 18:05:13", + SemverVersion.Parse("4.0.0"), + new[] { + SemverVersion.Parse("4.0.0"), + SemverVersion.Parse("3.6.0-0.2.8"), + }, + new[] { "cordova", "client", "cli" } + ); + + // Package added successfully + CheckPackage( + target, + byName, + 54151, + "mytestpackage98", + null, + null, + "08/14/2014 19:46:24", + SemverVersion.Parse("0.1.3"), + new[] { + SemverVersion.Parse("0.1.3") + }, + Enumerable.Empty() + ); + + } + + [TestMethod, Priority(0)] + public void CheckDatabaseUpdateArray() { + string cachePath = "NpmCacheUpdate"; + string registryPath = Path.Combine(cachePath, "registry", NpmGetCatalogCommand.RegistryCacheFilename); + + FileUtils.CopyDirectory(PackageCacheDirectory, cachePath); + + using (var reader = GetCatalogueReader(PackageCacheSinceJsonArrayFilename)) { + new NpmGetCatalogCommand(string.Empty, cachePath, false).ParseResultsAndAddToDatabase(reader, registryPath, RegistryUrl); + } + + IDictionary byName; + var target = GetTestPackageList(cachePath, out byName); + + Assert.AreEqual(90066, target.Count); + + // Package updated successfully + CheckPackage( + target, + byName, + 13986, + "cordova", + "Cordova command line interface tool", + "Anis Kadri", + "10/16/2014 18:05:13", + SemverVersion.Parse("4.0.0"), + new[] { + SemverVersion.Parse("4.0.0"), + SemverVersion.Parse("3.6.0-0.2.8"), + }, + new[] { "cordova", "client", "cli" } + ); + + // Package added successfully + CheckPackage( + target, + byName, + 253, + "9e-sass-lint", + "Makes sure you stick to our CSS rule order http://9elements.com/css-rule-order/", + "Sascha Gehlich", + "08/19/2015 08:21:25", + SemverVersion.Parse("0.0.12"), + new[] { + SemverVersion.Parse("0.0.12") + }, + new[] { "sass", "css", "lint", "rules" } + ); + + } + + [TestMethod, Priority(0)] + public void CheckPackageWithBuildPreReleaseInfo() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + CheckPackage( + target, + byName, + 65900, + "psc-cms-js", + "js library for Psc CMS (pscheit/psc-cms). shim reposistory for builds.", + "Philipp Scheit", + "01/07/2014 10:57:58", + SemverVersion.Parse("1.3.0-517056d"), + new[] { + SemverVersion.Parse("1.3.0-95847e2"), + SemverVersion.Parse("1.3.0-517056d"), + SemverVersion.Parse("1.4.0-e14fdf0") + }, + new[] { "cms", "framework" }); + } + + + private void CheckSensibleNumberOfNonZeroVersions(ICollection target) { + int sensibleVersionCount = 0; + var zero = SemverVersion.Parse("0.0.0"); + foreach (var package in target) { + if (package.Version != zero) { + ++sensibleVersionCount; + } + } + + // Let's say (it'll be much higher) but at least 25% of packages must have a sensible version number + Assert.IsTrue( + sensibleVersionCount > target.Count / 4, + string.Format("There are only {0} packages with version numbers other than {1}", sensibleVersionCount, zero)); + } + + private void CheckOnlyOneOfEachPackage(IEnumerable target) { + var packageCounts = new Dictionary>(); + foreach (IPackage package in target) { + if (!packageCounts.ContainsKey(package.Name)) { + packageCounts[package.Name] = new List(); + } + packageCounts[package.Name].Add(package); + } + + var moreThanOne = new List>(); + foreach (string name in packageCounts.Keys) { + if (packageCounts[name].Count > 1) { + moreThanOne.Add(packageCounts[name]); + } + } + + if (moreThanOne.Count > 0) { + var buff = new StringBuilder(); + foreach (var list in moreThanOne) { + if (buff.Length > 0) { + buff.Append(", "); + } + buff.Append("["); + foreach (var package in list) { + if (buff[buff.Length - 1] != '[') { + buff.Append(", "); + } + buff.Append("{\""); + buff.Append(package.Name); + buff.Append("\", \""); + buff.Append(package.Version); + buff.Append("\", \""); + buff.Append(package.Description); + buff.Append("\", \""); + buff.Append(package.Author); + buff.Append("\", \""); + buff.Append(string.Join(" ", package.Keywords)); + buff.Append("\"}"); + } + buff.Append("]"); + } + Assert.Fail(string.Format("Multiple package instances found: {0}", buff.ToString())); + } + } + + [TestMethod, Priority(0)] + public void CheckNonZeroPackageVersionsExist() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + CheckSensibleNumberOfNonZeroVersions(target); + } + + [TestMethod, Priority(0)] + public void CheckCorrectPackageCount() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + Assert.AreEqual(89924, target.Count, "Unexpected package count in catalogue list."); + } + + [TestMethod, Priority(0)] + public void CheckNoDuplicatePackages() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + CheckOnlyOneOfEachPackage(target); + } + + [TestMethod, Priority(0)] + public void CheckListAndDictByNameSameSize() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + Assert.AreEqual(target.Count, byName.Count, "Number of packages should be same in list and dictionary."); + } + + [TestMethod, Priority(0)] + public void CheckFirstPackageInCatalog() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + CheckPackage( + target, + byName, + 0, + "0", + null, + null, + "06/17/2014 06:38:43", + new SemverVersion(0, 0, 0), + new[] { + new SemverVersion(0, 0, 0) + }, + Enumerable.Empty()); + } + + [TestMethod, Priority(0)] + public void CheckLastPackageInCatalog_zzz() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + CheckPackage( + target, + byName, + 89923, + "z_test", + "zhanghao test", + "zhanghao", + "01/02/2014 03:28:23", + new SemverVersion(1, 0, 0), + new[] { + new SemverVersion(1, 0, 0) + }, + Enumerable.Empty()); + } + + [TestMethod, Priority(0)] + public void CheckPackageEqualsInDescription() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + CheckPackage( + target, + byName, + 62060, + "particularizable", + "particularizable ================ `enumerable` was taken.", + "ELLIOTTCABLE", + "06/11/2013 22:48:35", + SemverVersion.Parse("1.0.0"), + new[] { + SemverVersion.Parse("1.0.0") + }, + Enumerable.Empty()); + } + + [TestMethod, Priority(0)] + public void CheckPackageNoDescriptionAuthorVersion() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + + CheckPackage( + target, + byName, + 25, + "122", + null, + null, + "03/03/2014 16:52:16", + SemverVersion.UnknownVersion, + Enumerable.Empty(), + Enumerable.Empty()); + } + + [TestMethod, Priority(0)] + public void CheckPackageNoDescription() { + IDictionary byName; + var target = GetTestPackageList(PackageCacheDirectory, out byName); + + CheckPackage( + target, + byName, + 455, + "active-client", + null, + "Subbu Allamaraju", + "01/03/2011 21:21:12", + SemverVersion.Parse("0.1.1"), + new[] { + SemverVersion.Parse("0.1.1") + }, + Enumerable.Empty()); + } + + private IList GetFilteredPackageList(string filterString) { + var filter = new DatabasePackageCatalogFilter(PackageCacheFilename); + return filter.Filter(filterString).ToList(); + } + + [TestMethod, Priority(0)] + public void TestFilterString() { + const string filterString = "express"; + var results = GetFilteredPackageList(filterString); + Assert.IsTrue(results.Count > 0, string.Format("Should be some filter results for '{0}'.", filterString)); + foreach (var package in results) { + bool match = false; + if (package.Name.ToLower().Contains(filterString)) { + match = true; + } else if (null != package.Description && package.Description.ToLower().Contains(filterString)) { + match = true; + } else { + if (package.Keywords.Any(keyword => keyword.ToLower().Contains(filterString))) { + match = true; + } + } + + Assert.IsTrue(match, string.Format("Found no match for filter string '{0}' in package '{1}'.", filterString, package.Name)); + } + } + + [Ignore] + [TestMethod, Priority(0)] + public void TestFilterStringWithHyphens() { + const string + filterStringWithHyphenMiddle = "grunt-contrib", + filterStringWithHyphenSuffix = "amazing-", + filterStringWithHyphenPrefix = "-grunt"; + + var results = GetFilteredPackageList(filterStringWithHyphenMiddle); + Assert.AreEqual(filterStringWithHyphenMiddle, results.First().Name, "Exact filter string match should be first in list."); + + results = GetFilteredPackageList(filterStringWithHyphenSuffix); + Assert.AreEqual(filterStringWithHyphenSuffix, results.First().Name, "Exact filter string match (including suffix) should be first."); + + // Package names cannot begin with a hyphen, but we would expect the first result to at least include the filter string. + results = GetFilteredPackageList(filterStringWithHyphenPrefix); + Assert.IsTrue(results.First().Name.Contains(filterStringWithHyphenPrefix), "Filter string match (including prefix) should be first"); + } + + private void CheckRegexFilterResults(string filterString, IList results) { + const string expectedMatch = "express"; + Assert.IsTrue(results.Count > 0, string.Format("Should be some filter results for '{0}'.", filterString)); + foreach (var package in results) { + bool match = false; + if (package.Name.ToLower() == expectedMatch) { + match = true; + } else if (null != package.Description && package.Description.ToLower() == expectedMatch) { + match = true; + } else { + if (package.Keywords.Any(keyword => keyword.ToLower() == expectedMatch)) { + match = true; + } + } + + Assert.IsTrue(match, string.Format("Found no match for filter regex '{0}' in package '{1}'.", filterString, package.Name)); + } + } + + private void TestFilterRegex(string filterString) { + CheckRegexFilterResults(filterString, GetFilteredPackageList(filterString)); + } + + [TestMethod, Priority(0)] + public void TestFilterRegex() { + TestFilterRegex("/^express$"); + } + + [TestMethod, Priority(0)] + public void TestFilterRegexTrailingSlash() { + TestFilterRegex("/^express$/"); + } + } +} diff --git a/Nodejs/Tests/NpmTests/PackageJsonDependencyTests.cs b/Nodejs/Tests/NpmTests/PackageJsonDependencyTests.cs index 56eabe25d..ff38a6c4b 100644 --- a/Nodejs/Tests/NpmTests/PackageJsonDependencyTests.cs +++ b/Nodejs/Tests/NpmTests/PackageJsonDependencyTests.cs @@ -1,527 +1,527 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Collections.Generic; -using System.Linq; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - [TestClass] - public class PackageJsonDependencyTests : AbstractPackageJsonTests { - /* - { "dependencies" : - { "foo" : "1.0.0 - 2.9999.9999" - , "bar" : ">=1.0.2 <2.1.2" - , "baz" : ">1.0.2 <=2.3.4" - , "boo" : "2.0.1" - , "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0" - , "asd" : "http://asdf.com/asdf.tar.gz" - , "til" : "~1.2" - , "elf" : "~1.2.3" - , "two" : "2.x" - , "thr" : "3.3.x" - } - } - */ - - private const string PkgDependencies = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""dependencies"" : - { ""foo"" : ""1.0.0 - 2.9999.9999"" - , ""bar"" : "">=1.0.2 <2.1.2"" - , ""baz"" : "">1.0.2 <=2.3.4"" - , ""boo"" : ""2.0.1"" - , ""qux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" - , ""asd"" : ""http://asdf.com/asdf.tar.gz"" - , ""til"" : ""~1.2"" - , ""elf"" : ""~1.2.3"" - , ""two"" : ""2.x"" - , ""thr"" : ""3.3.x"" - , ""git"" : ""git://github.com/user/project.git#commit-ish"" - , ""gitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" - , ""gitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" - , ""githttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" - , ""githttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" - , ""github"" : ""username/projectname"" - } -}"; - - private const string PkgAllTheDependencies = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""dependencies"" : - { ""foo"" : ""1.0.0 - 2.9999.9999"" - , ""bar"" : "">=1.0.2 <2.1.2"" - , ""baz"" : "">1.0.2 <=2.3.4"" - , ""boo"" : ""2.0.1"" - , ""qux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" - , ""asd"" : ""http://asdf.com/asdf.tar.gz"" - , ""til"" : ""~1.2"" - , ""elf"" : ""~1.2.3"" - , ""two"" : ""2.x"" - , ""thr"" : ""3.3.x"" - , ""git"" : ""git://github.com/user/project.git#commit-ish"" - , ""gitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" - , ""gitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" - , ""githttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" - , ""githttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" - , ""github"" : ""username/projectname"" - }, - ""devDependencies"" : - { ""devfoo"" : ""1.0.0 - 2.9999.9999"" - , ""devbar"" : "">=1.0.2 <2.1.2"" - , ""devbaz"" : "">1.0.2 <=2.3.4"" - , ""devboo"" : ""2.0.1"" - , ""devqux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" - , ""devasd"" : ""http://asdf.com/asdf.tar.gz"" - , ""devtil"" : ""~1.2"" - , ""develf"" : ""~1.2.3"" - , ""devtwo"" : ""2.x"" - , ""devthr"" : ""3.3.x"" - , ""devgit"" : ""git://github.com/user/project.git#commit-ish"" - , ""devgitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" - , ""devgitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" - , ""devgithttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" - , ""devgithttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" - , ""devgithub"" : ""username/projectname"" - }, - ""bundledDependencies"" : - [ ""foo"" - , ""bar"" - , ""baz"" - , ""boo"" - , ""qux"" - , ""asd"" - , ""til"" - , ""elf"" - , ""two"" - , ""thr"" - , ""git"" - , ""gitssh"" - , ""gitssh2"" - , ""githttp"" - , ""githttps"" - , ""github"" - ], - ""optionalDependencies"" : - { ""optionalfoo"" : ""1.0.0 - 2.9999.9999"" - , ""optionalbar"" : "">=1.0.2 <2.1.2"" - , ""optionalbaz"" : "">1.0.2 <=2.3.4"" - , ""optionalboo"" : ""2.0.1"" - , ""optionalqux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" - , ""optionalasd"" : ""http://asdf.com/asdf.tar.gz"" - , ""optionaltil"" : ""~1.2"" - , ""optionalelf"" : ""~1.2.3"" - , ""optionaltwo"" : ""2.x"" - , ""optionalthr"" : ""3.3.x"" - , ""optionalgit"" : ""git://github.com/user/project.git#commit-ish"" - , ""optionalgitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" - , ""optionalgitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" - , ""optionalgithttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" - , ""optionalgithttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" - , ""optionalgithub"" : ""username/projectname"" - } -}"; - - private const string PkgBundleDependencies = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""dependencies"" : - { ""foo"" : ""1.0.0 - 2.9999.9999"" - , ""bar"" : "">=1.0.2 <2.1.2"" - , ""baz"" : "">1.0.2 <=2.3.4"" - , ""boo"" : ""2.0.1"" - , ""qux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" - , ""asd"" : ""http://asdf.com/asdf.tar.gz"" - , ""til"" : ""~1.2"" - , ""elf"" : ""~1.2.3"" - , ""two"" : ""2.x"" - , ""thr"" : ""3.3.x"" - , ""git"" : ""git://github.com/user/project.git#commit-ish"" - , ""gitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" - , ""gitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" - , ""githttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" - , ""githttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" - , ""github"" : ""username/projectname"" - }, - ""devDependencies"" : - { ""devfoo"" : ""1.0.0 - 2.9999.9999"" - , ""devbar"" : "">=1.0.2 <2.1.2"" - , ""devbaz"" : "">1.0.2 <=2.3.4"" - , ""devboo"" : ""2.0.1"" - , ""devqux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" - , ""devasd"" : ""http://asdf.com/asdf.tar.gz"" - , ""devtil"" : ""~1.2"" - , ""develf"" : ""~1.2.3"" - , ""devtwo"" : ""2.x"" - , ""devthr"" : ""3.3.x"" - , ""devgit"" : ""git://github.com/user/project.git#commit-ish"" - , ""devgitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" - , ""devgitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" - , ""devgithttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" - , ""devgithttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" - , ""devgithub"" : ""username/projectname"" - }, - ""bundleDependencies"" : - [ ""foo"" - , ""bar"" - , ""baz"" - , ""boo"" - , ""qux"" - , ""asd"" - , ""til"" - , ""elf"" - , ""two"" - , ""thr"" - , ""git"" - , ""gitssh"" - , ""gitssh2"" - , ""githttp"" - , ""githttps"" - , ""github"" - ] -}"; - - private static readonly string[][] VersionRangeDependencies = new[]{ - new[]{"foo", "1.0.0 - 2.9999.9999"} - , - new[]{"bar", ">=1.0.2 <2.1.2"} - , - new[]{"baz", ">1.0.2 <=2.3.4"} - , - new[]{"boo", "2.0.1"} - , - new[]{"qux", "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"} - , - new[]{"til", "~1.2"} - , - new[]{"elf", "~1.2.3"} - , - new[]{"two", "2.x"} - , - new[]{"thr", "3.3.x"} - }; - - private static readonly string[][] UrlDependencies = new[]{ - new[]{"asd", "http://asdf.com/asdf.tar.gz"} - , - new[]{"git", "git://github.com/user/project.git#commit-ish"} - , - new[]{"gitssh", "git+ssh://user@hostname:project.git#commit-ish"} - , - new[]{"gitssh2", "git+ssh://user@hostname/project.git#commit-ish"} - , - new[]{"githttp", "git+http://user@hostname/project/blah.git#commit-ish"} - , - new[]{"githttps", "git+https://user@hostname/project/blah.git#commit-ish"} - , - new[]{"github", "username/projectname"} - }; - - private static readonly string[][] OptionalVersionRangeDependencies = new[]{ - new[]{"optionalfoo", "1.0.0 - 2.9999.9999"} - , - new[]{"optionalbar", ">=1.0.2 <2.1.2"} - , - new[]{"optionalbaz", ">1.0.2 <=2.3.4"} - , - new[]{"optionalboo", "2.0.1"} - , - new[]{"optionalqux", "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"} - , - new[]{"optionaltil", "~1.2"} - , - new[]{"optionalelf", "~1.2.3"} - , - new[]{"optionaltwo", "2.x"} - , - new[]{"optionalthr", "3.3.x"} - }; - - private static readonly string[][] OptionalUrlDependencies = new[]{ - new[]{"optionalasd", "http://asdf.com/asdf.tar.gz"} - , - new[]{"optionalgit", "git://github.com/user/project.git#commit-ish"} - , - new[]{"optionalgitssh", "git+ssh://user@hostname:project.git#commit-ish"} - , - new[]{"optionalgitssh2", "git+ssh://user@hostname/project.git#commit-ish"} - , - new[]{"optionalgithttp", "git+http://user@hostname/project/blah.git#commit-ish"} - , - new[]{"optionalgithttps", "git+https://user@hostname/project/blah.git#commit-ish"} - , - new[]{"optionalgithub", "username/projectname"} - }; - - private static readonly string[][] DevVersionRangeDependencies = new[]{ - new[]{"devfoo", "1.0.0 - 2.9999.9999"} - , - new[]{"devbar", ">=1.0.2 <2.1.2"} - , - new[]{"devbaz", ">1.0.2 <=2.3.4"} - , - new[]{"devboo", "2.0.1"} - , - new[]{"devqux", "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"} - , - new[]{"devtil", "~1.2"} - , - new[]{"develf", "~1.2.3"} - , - new[]{"devtwo", "2.x"} - , - new[]{"devthr", "3.3.x"} - }; - - private static readonly string[][] DevUrlDependencies = new[]{ - new[]{"devasd", "http://asdf.com/asdf.tar.gz"} - , - new[]{"devgit", "git://github.com/user/project.git#commit-ish"} - , - new[]{"devgitssh", "git+ssh://user@hostname:project.git#commit-ish"} - , - new[]{"devgitssh2", "git+ssh://user@hostname/project.git#commit-ish"} - , - new[]{"devgithttp", "git+http://user@hostname/project/blah.git#commit-ish"} - , - new[]{"devgithttps", "git+https://user@hostname/project/blah.git#commit-ish"} - , - new[]{"devgithub", "username/projectname"} - }; - - private static readonly string[] ExpectedBundledDependencies = new[]{ - "foo" - , - "bar" - , - "baz" - , - "boo" - , - "qux" - , - "til" - , - "elf" - , - "two" - , - "thr" - , - "asd" - , - "git" - , - "gitssh" - , - "gitssh2" - , - "githttp" - , - "githttps" - , - "github" - }; - - private void CheckEmptyDependenciesNotNull(IDependencies dependencies) { - Assert.IsNotNull(dependencies, "Dependencies should not be null."); - Assert.AreEqual(0, dependencies.Count, "Should not be any dependencies."); - } - - [TestMethod, Priority(0)] - public void TestReadEmptyDependenciesNotNull() { - CheckEmptyDependenciesNotNull(LoadFrom(PkgSimple).Dependencies); - } - - [TestMethod, Priority(0)] - public void TestReadEmptyDevDependenciesNotNull() { - CheckEmptyDependenciesNotNull(LoadFrom(PkgSimple).DevDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadEmptyOptionalDependenciesNotNull() { - CheckEmptyDependenciesNotNull(LoadFrom(PkgSimple).OptionalDependencies); - } - - private void CheckDependencies( - IDictionary retrieved, - IEnumerable expected, - int expectedCount) { - Assert.AreEqual(expectedCount, retrieved.Count, "Retrieved dependency count mismatch."); - foreach (var pair in expected) { - var dependency = retrieved[pair[0]]; - Assert.IsNotNull( - dependency, - string.Format("Should have found a dependency on package '{0}'.", pair[0])); - - if (pair[1].IndexOf('/') < 0) // i.e., the dependency isn't specified with a URL - { - Assert.IsNull( - dependency.Url, - string.Format("Dependency on package '{0}' should not specify a URL.", pair[0])); - - Assert.AreEqual( - pair[1], - dependency.VersionRangeText, - string.Format("Version range mismatch for package '{0}'.", pair[0])); - } else { - Assert.IsNull( - dependency.VersionRangeText, - "Dependency with URL should not specify version range text."); - IDependencyUrl url = dependency.Url; - Assert.IsNotNull(url, "Dependency URL should not be null."); - string address = url.Address; - Assert.IsNotNull(address, "Dependency URL address should not be null."); - var index = address.IndexOf("://"); - if (index < 0) { - Assert.AreEqual(DependencyUrlType.GitHub, url.Type, "Dependency URL type should be GitHub"); - } else { - var prefix = address.Substring(0, index); - switch (prefix) { - case "http": - Assert.AreEqual(DependencyUrlType.Http, url.Type, "Dependency URL type should be Http."); - break; - - case "git": - Assert.AreEqual(DependencyUrlType.Git, url.Type, "Dependency URL type should be Git."); - break; - - case "git+ssh": - Assert.AreEqual( - DependencyUrlType.GitSsh, - url.Type, - "Dependency URL type should be GitSsh."); - break; - - case "git+http": - Assert.AreEqual( - DependencyUrlType.GitHttp, - url.Type, - "Dependency URL type should be GitHttp."); - break; - - case "git+https": - Assert.AreEqual( - DependencyUrlType.GitHttps, - url.Type, - "Dependency URL type should be GitHttps."); - break; - - default: - Assert.Fail(string.Format("Unrecognised URL prefix: {0}", prefix)); - break; - } - } - } - } - } - - private void CheckDependencies(IDictionary retrieved, IEnumerable expected) { - CheckDependencies(retrieved, expected, 16); - } - - private IDictionary ReadDependencies(IDependencies dependencies, int expectedCount) { - Assert.AreEqual(expectedCount, dependencies.Count, "Dependency count mismatch."); - - var retrieved = new Dictionary(); - foreach (var dependency in dependencies) { - retrieved[dependency.Name] = dependency; - } - return retrieved; - } - - private IDictionary ReadDependencies(IDependencies dependencies) { - return ReadDependencies(dependencies, 16); - } - - private IDictionary ReadDependencies() { - return ReadDependencies(LoadFrom(PkgDependencies).Dependencies); - } - - private IDictionary ReadDevDependencies() { - return ReadDependencies(LoadFrom(PkgAllTheDependencies).DevDependencies); - } - - private IDictionary ReadOptionalDependencies() { - return ReadDependencies(LoadFrom(PkgAllTheDependencies).OptionalDependencies); - } - - private IDictionary ReadAllDependencies() { - return ReadDependencies(LoadFrom(PkgAllTheDependencies).AllDependencies, 48); - } - - [TestMethod, Priority(0)] - public void TestReadDependenciesWithVersionRange() { - CheckDependencies(ReadDependencies(), VersionRangeDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadDependenciesWithUrls() { - CheckDependencies(ReadDependencies(), UrlDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadDevDependenciesWithVersionRange() { - CheckDependencies(ReadDevDependencies(), DevVersionRangeDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadDevDependenciesWithUrls() { - CheckDependencies(ReadDevDependencies(), DevUrlDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadBundledDependencies() { - CheckStringArrayContents( - LoadFrom(PkgAllTheDependencies).BundledDependencies, - 16, - ExpectedBundledDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadBundleDependencies() { - CheckStringArrayContents( - LoadFrom(PkgBundleDependencies).BundledDependencies, - 16, - ExpectedBundledDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadOptionalDependenciesWithVersionRange() { - CheckDependencies(ReadOptionalDependencies(), OptionalVersionRangeDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadOptionalDependenciesWithUrls() { - CheckDependencies(ReadOptionalDependencies(), OptionalUrlDependencies); - } - - [TestMethod, Priority(0)] - public void TestReadAllDependencies() { - CheckDependencies( - ReadAllDependencies(), - UrlDependencies.Concat(VersionRangeDependencies) - .Concat(DevUrlDependencies) - .Concat(DevVersionRangeDependencies) - .Concat(OptionalUrlDependencies) - .Concat(OptionalVersionRangeDependencies), - 48); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Collections.Generic; +using System.Linq; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + [TestClass] + public class PackageJsonDependencyTests : AbstractPackageJsonTests { + /* + { "dependencies" : + { "foo" : "1.0.0 - 2.9999.9999" + , "bar" : ">=1.0.2 <2.1.2" + , "baz" : ">1.0.2 <=2.3.4" + , "boo" : "2.0.1" + , "qux" : "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0" + , "asd" : "http://asdf.com/asdf.tar.gz" + , "til" : "~1.2" + , "elf" : "~1.2.3" + , "two" : "2.x" + , "thr" : "3.3.x" + } + } + */ + + private const string PkgDependencies = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""dependencies"" : + { ""foo"" : ""1.0.0 - 2.9999.9999"" + , ""bar"" : "">=1.0.2 <2.1.2"" + , ""baz"" : "">1.0.2 <=2.3.4"" + , ""boo"" : ""2.0.1"" + , ""qux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" + , ""asd"" : ""http://asdf.com/asdf.tar.gz"" + , ""til"" : ""~1.2"" + , ""elf"" : ""~1.2.3"" + , ""two"" : ""2.x"" + , ""thr"" : ""3.3.x"" + , ""git"" : ""git://github.com/user/project.git#commit-ish"" + , ""gitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" + , ""gitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" + , ""githttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" + , ""githttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" + , ""github"" : ""username/projectname"" + } +}"; + + private const string PkgAllTheDependencies = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""dependencies"" : + { ""foo"" : ""1.0.0 - 2.9999.9999"" + , ""bar"" : "">=1.0.2 <2.1.2"" + , ""baz"" : "">1.0.2 <=2.3.4"" + , ""boo"" : ""2.0.1"" + , ""qux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" + , ""asd"" : ""http://asdf.com/asdf.tar.gz"" + , ""til"" : ""~1.2"" + , ""elf"" : ""~1.2.3"" + , ""two"" : ""2.x"" + , ""thr"" : ""3.3.x"" + , ""git"" : ""git://github.com/user/project.git#commit-ish"" + , ""gitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" + , ""gitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" + , ""githttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" + , ""githttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" + , ""github"" : ""username/projectname"" + }, + ""devDependencies"" : + { ""devfoo"" : ""1.0.0 - 2.9999.9999"" + , ""devbar"" : "">=1.0.2 <2.1.2"" + , ""devbaz"" : "">1.0.2 <=2.3.4"" + , ""devboo"" : ""2.0.1"" + , ""devqux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" + , ""devasd"" : ""http://asdf.com/asdf.tar.gz"" + , ""devtil"" : ""~1.2"" + , ""develf"" : ""~1.2.3"" + , ""devtwo"" : ""2.x"" + , ""devthr"" : ""3.3.x"" + , ""devgit"" : ""git://github.com/user/project.git#commit-ish"" + , ""devgitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" + , ""devgitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" + , ""devgithttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" + , ""devgithttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" + , ""devgithub"" : ""username/projectname"" + }, + ""bundledDependencies"" : + [ ""foo"" + , ""bar"" + , ""baz"" + , ""boo"" + , ""qux"" + , ""asd"" + , ""til"" + , ""elf"" + , ""two"" + , ""thr"" + , ""git"" + , ""gitssh"" + , ""gitssh2"" + , ""githttp"" + , ""githttps"" + , ""github"" + ], + ""optionalDependencies"" : + { ""optionalfoo"" : ""1.0.0 - 2.9999.9999"" + , ""optionalbar"" : "">=1.0.2 <2.1.2"" + , ""optionalbaz"" : "">1.0.2 <=2.3.4"" + , ""optionalboo"" : ""2.0.1"" + , ""optionalqux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" + , ""optionalasd"" : ""http://asdf.com/asdf.tar.gz"" + , ""optionaltil"" : ""~1.2"" + , ""optionalelf"" : ""~1.2.3"" + , ""optionaltwo"" : ""2.x"" + , ""optionalthr"" : ""3.3.x"" + , ""optionalgit"" : ""git://github.com/user/project.git#commit-ish"" + , ""optionalgitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" + , ""optionalgitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" + , ""optionalgithttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" + , ""optionalgithttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" + , ""optionalgithub"" : ""username/projectname"" + } +}"; + + private const string PkgBundleDependencies = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""dependencies"" : + { ""foo"" : ""1.0.0 - 2.9999.9999"" + , ""bar"" : "">=1.0.2 <2.1.2"" + , ""baz"" : "">1.0.2 <=2.3.4"" + , ""boo"" : ""2.0.1"" + , ""qux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" + , ""asd"" : ""http://asdf.com/asdf.tar.gz"" + , ""til"" : ""~1.2"" + , ""elf"" : ""~1.2.3"" + , ""two"" : ""2.x"" + , ""thr"" : ""3.3.x"" + , ""git"" : ""git://github.com/user/project.git#commit-ish"" + , ""gitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" + , ""gitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" + , ""githttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" + , ""githttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" + , ""github"" : ""username/projectname"" + }, + ""devDependencies"" : + { ""devfoo"" : ""1.0.0 - 2.9999.9999"" + , ""devbar"" : "">=1.0.2 <2.1.2"" + , ""devbaz"" : "">1.0.2 <=2.3.4"" + , ""devboo"" : ""2.0.1"" + , ""devqux"" : ""<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"" + , ""devasd"" : ""http://asdf.com/asdf.tar.gz"" + , ""devtil"" : ""~1.2"" + , ""develf"" : ""~1.2.3"" + , ""devtwo"" : ""2.x"" + , ""devthr"" : ""3.3.x"" + , ""devgit"" : ""git://github.com/user/project.git#commit-ish"" + , ""devgitssh"" : ""git+ssh://user@hostname:project.git#commit-ish"" + , ""devgitssh2"" : ""git+ssh://user@hostname/project.git#commit-ish"" + , ""devgithttp"" : ""git+http://user@hostname/project/blah.git#commit-ish"" + , ""devgithttps"" : ""git+https://user@hostname/project/blah.git#commit-ish"" + , ""devgithub"" : ""username/projectname"" + }, + ""bundleDependencies"" : + [ ""foo"" + , ""bar"" + , ""baz"" + , ""boo"" + , ""qux"" + , ""asd"" + , ""til"" + , ""elf"" + , ""two"" + , ""thr"" + , ""git"" + , ""gitssh"" + , ""gitssh2"" + , ""githttp"" + , ""githttps"" + , ""github"" + ] +}"; + + private static readonly string[][] VersionRangeDependencies = new[]{ + new[]{"foo", "1.0.0 - 2.9999.9999"} + , + new[]{"bar", ">=1.0.2 <2.1.2"} + , + new[]{"baz", ">1.0.2 <=2.3.4"} + , + new[]{"boo", "2.0.1"} + , + new[]{"qux", "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"} + , + new[]{"til", "~1.2"} + , + new[]{"elf", "~1.2.3"} + , + new[]{"two", "2.x"} + , + new[]{"thr", "3.3.x"} + }; + + private static readonly string[][] UrlDependencies = new[]{ + new[]{"asd", "http://asdf.com/asdf.tar.gz"} + , + new[]{"git", "git://github.com/user/project.git#commit-ish"} + , + new[]{"gitssh", "git+ssh://user@hostname:project.git#commit-ish"} + , + new[]{"gitssh2", "git+ssh://user@hostname/project.git#commit-ish"} + , + new[]{"githttp", "git+http://user@hostname/project/blah.git#commit-ish"} + , + new[]{"githttps", "git+https://user@hostname/project/blah.git#commit-ish"} + , + new[]{"github", "username/projectname"} + }; + + private static readonly string[][] OptionalVersionRangeDependencies = new[]{ + new[]{"optionalfoo", "1.0.0 - 2.9999.9999"} + , + new[]{"optionalbar", ">=1.0.2 <2.1.2"} + , + new[]{"optionalbaz", ">1.0.2 <=2.3.4"} + , + new[]{"optionalboo", "2.0.1"} + , + new[]{"optionalqux", "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"} + , + new[]{"optionaltil", "~1.2"} + , + new[]{"optionalelf", "~1.2.3"} + , + new[]{"optionaltwo", "2.x"} + , + new[]{"optionalthr", "3.3.x"} + }; + + private static readonly string[][] OptionalUrlDependencies = new[]{ + new[]{"optionalasd", "http://asdf.com/asdf.tar.gz"} + , + new[]{"optionalgit", "git://github.com/user/project.git#commit-ish"} + , + new[]{"optionalgitssh", "git+ssh://user@hostname:project.git#commit-ish"} + , + new[]{"optionalgitssh2", "git+ssh://user@hostname/project.git#commit-ish"} + , + new[]{"optionalgithttp", "git+http://user@hostname/project/blah.git#commit-ish"} + , + new[]{"optionalgithttps", "git+https://user@hostname/project/blah.git#commit-ish"} + , + new[]{"optionalgithub", "username/projectname"} + }; + + private static readonly string[][] DevVersionRangeDependencies = new[]{ + new[]{"devfoo", "1.0.0 - 2.9999.9999"} + , + new[]{"devbar", ">=1.0.2 <2.1.2"} + , + new[]{"devbaz", ">1.0.2 <=2.3.4"} + , + new[]{"devboo", "2.0.1"} + , + new[]{"devqux", "<1.0.0 || >=2.3.1 <2.4.5 || >=2.5.2 <3.0.0"} + , + new[]{"devtil", "~1.2"} + , + new[]{"develf", "~1.2.3"} + , + new[]{"devtwo", "2.x"} + , + new[]{"devthr", "3.3.x"} + }; + + private static readonly string[][] DevUrlDependencies = new[]{ + new[]{"devasd", "http://asdf.com/asdf.tar.gz"} + , + new[]{"devgit", "git://github.com/user/project.git#commit-ish"} + , + new[]{"devgitssh", "git+ssh://user@hostname:project.git#commit-ish"} + , + new[]{"devgitssh2", "git+ssh://user@hostname/project.git#commit-ish"} + , + new[]{"devgithttp", "git+http://user@hostname/project/blah.git#commit-ish"} + , + new[]{"devgithttps", "git+https://user@hostname/project/blah.git#commit-ish"} + , + new[]{"devgithub", "username/projectname"} + }; + + private static readonly string[] ExpectedBundledDependencies = new[]{ + "foo" + , + "bar" + , + "baz" + , + "boo" + , + "qux" + , + "til" + , + "elf" + , + "two" + , + "thr" + , + "asd" + , + "git" + , + "gitssh" + , + "gitssh2" + , + "githttp" + , + "githttps" + , + "github" + }; + + private void CheckEmptyDependenciesNotNull(IDependencies dependencies) { + Assert.IsNotNull(dependencies, "Dependencies should not be null."); + Assert.AreEqual(0, dependencies.Count, "Should not be any dependencies."); + } + + [TestMethod, Priority(0)] + public void TestReadEmptyDependenciesNotNull() { + CheckEmptyDependenciesNotNull(LoadFrom(PkgSimple).Dependencies); + } + + [TestMethod, Priority(0)] + public void TestReadEmptyDevDependenciesNotNull() { + CheckEmptyDependenciesNotNull(LoadFrom(PkgSimple).DevDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadEmptyOptionalDependenciesNotNull() { + CheckEmptyDependenciesNotNull(LoadFrom(PkgSimple).OptionalDependencies); + } + + private void CheckDependencies( + IDictionary retrieved, + IEnumerable expected, + int expectedCount) { + Assert.AreEqual(expectedCount, retrieved.Count, "Retrieved dependency count mismatch."); + foreach (var pair in expected) { + var dependency = retrieved[pair[0]]; + Assert.IsNotNull( + dependency, + string.Format("Should have found a dependency on package '{0}'.", pair[0])); + + if (pair[1].IndexOf('/') < 0) // i.e., the dependency isn't specified with a URL + { + Assert.IsNull( + dependency.Url, + string.Format("Dependency on package '{0}' should not specify a URL.", pair[0])); + + Assert.AreEqual( + pair[1], + dependency.VersionRangeText, + string.Format("Version range mismatch for package '{0}'.", pair[0])); + } else { + Assert.IsNull( + dependency.VersionRangeText, + "Dependency with URL should not specify version range text."); + IDependencyUrl url = dependency.Url; + Assert.IsNotNull(url, "Dependency URL should not be null."); + string address = url.Address; + Assert.IsNotNull(address, "Dependency URL address should not be null."); + var index = address.IndexOf("://"); + if (index < 0) { + Assert.AreEqual(DependencyUrlType.GitHub, url.Type, "Dependency URL type should be GitHub"); + } else { + var prefix = address.Substring(0, index); + switch (prefix) { + case "http": + Assert.AreEqual(DependencyUrlType.Http, url.Type, "Dependency URL type should be Http."); + break; + + case "git": + Assert.AreEqual(DependencyUrlType.Git, url.Type, "Dependency URL type should be Git."); + break; + + case "git+ssh": + Assert.AreEqual( + DependencyUrlType.GitSsh, + url.Type, + "Dependency URL type should be GitSsh."); + break; + + case "git+http": + Assert.AreEqual( + DependencyUrlType.GitHttp, + url.Type, + "Dependency URL type should be GitHttp."); + break; + + case "git+https": + Assert.AreEqual( + DependencyUrlType.GitHttps, + url.Type, + "Dependency URL type should be GitHttps."); + break; + + default: + Assert.Fail(string.Format("Unrecognised URL prefix: {0}", prefix)); + break; + } + } + } + } + } + + private void CheckDependencies(IDictionary retrieved, IEnumerable expected) { + CheckDependencies(retrieved, expected, 16); + } + + private IDictionary ReadDependencies(IDependencies dependencies, int expectedCount) { + Assert.AreEqual(expectedCount, dependencies.Count, "Dependency count mismatch."); + + var retrieved = new Dictionary(); + foreach (var dependency in dependencies) { + retrieved[dependency.Name] = dependency; + } + return retrieved; + } + + private IDictionary ReadDependencies(IDependencies dependencies) { + return ReadDependencies(dependencies, 16); + } + + private IDictionary ReadDependencies() { + return ReadDependencies(LoadFrom(PkgDependencies).Dependencies); + } + + private IDictionary ReadDevDependencies() { + return ReadDependencies(LoadFrom(PkgAllTheDependencies).DevDependencies); + } + + private IDictionary ReadOptionalDependencies() { + return ReadDependencies(LoadFrom(PkgAllTheDependencies).OptionalDependencies); + } + + private IDictionary ReadAllDependencies() { + return ReadDependencies(LoadFrom(PkgAllTheDependencies).AllDependencies, 48); + } + + [TestMethod, Priority(0)] + public void TestReadDependenciesWithVersionRange() { + CheckDependencies(ReadDependencies(), VersionRangeDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadDependenciesWithUrls() { + CheckDependencies(ReadDependencies(), UrlDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadDevDependenciesWithVersionRange() { + CheckDependencies(ReadDevDependencies(), DevVersionRangeDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadDevDependenciesWithUrls() { + CheckDependencies(ReadDevDependencies(), DevUrlDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadBundledDependencies() { + CheckStringArrayContents( + LoadFrom(PkgAllTheDependencies).BundledDependencies, + 16, + ExpectedBundledDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadBundleDependencies() { + CheckStringArrayContents( + LoadFrom(PkgBundleDependencies).BundledDependencies, + 16, + ExpectedBundledDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadOptionalDependenciesWithVersionRange() { + CheckDependencies(ReadOptionalDependencies(), OptionalVersionRangeDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadOptionalDependenciesWithUrls() { + CheckDependencies(ReadOptionalDependencies(), OptionalUrlDependencies); + } + + [TestMethod, Priority(0)] + public void TestReadAllDependencies() { + CheckDependencies( + ReadAllDependencies(), + UrlDependencies.Concat(VersionRangeDependencies) + .Concat(DevUrlDependencies) + .Concat(DevVersionRangeDependencies) + .Concat(OptionalUrlDependencies) + .Concat(OptionalVersionRangeDependencies), + 48); + } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/PackageJsonTests.cs b/Nodejs/Tests/NpmTests/PackageJsonTests.cs index a939e1c3c..3c30dfb78 100644 --- a/Nodejs/Tests/NpmTests/PackageJsonTests.cs +++ b/Nodejs/Tests/NpmTests/PackageJsonTests.cs @@ -1,446 +1,446 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Newtonsoft.Json; - -namespace NpmTests { - [TestClass] - public class PackageJsonTests : AbstractPackageJsonTests { - private const string PkgSimpleBugs = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""bugs"": ""http://www.mybugtracker.com/"" -}"; - - private const string PkgMisSpelledAuthor = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""author"": { - ""misspelledname"": ""Firstname Lastname"" - } -}"; - - private const string PkgSingleAuthorField = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""author"": { - ""name"": ""Firstname Lastname"" - } -}"; - - private const string PkgSingleLicenseType = @"{ - ""name"": ""TestPkg"", - ""version"": ""0.1.0"", - ""license"" : ""BSD"" -}"; - - private const string PkgStartScript = @"{ - ""name"": ""ScriptPkg"", - ""version"": ""1.2.3"", - ""scripts"": {""start"": ""node server.js""} -}"; - - private const string PkgLargeCompliant = @"{ - ""name"": ""mypackage"", - ""version"": ""0.7.0"", - ""description"": ""Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package."", - ""author"": { - ""name"": ""Firstname Lastname"", - ""email"": ""firstname@lastname.com"", - ""url"": ""http://firstnamelastname.com"" - }, - ""keywords"": [ - ""package"", - ""example"" - ], - ""homepage"": ""http://www.mypackagehomepage.com/"", - ""maintainers"": [ - { - ""name"": ""Bill Smith"", - ""email"": ""bills@example.com"", - ""web"": ""http://www.example.com"" - } - ], - ""contributors"": [ - { - ""name"": ""Mary Brown"", - ""email"": ""maryb@embedthis.com"", - ""web"": ""http://www.embedthis.com"" - } - ], - ""bugs"": { - ""email"": ""dev@example.com"", - ""url"": ""http://www.example.com/bugs"" - }, - ""licenses"": [ - { - ""type"": ""GPLv2"", - ""url"": ""http://www.example.org/licenses/gpl.html"" - } - ], - ""repositories"": [ - { - ""type"": ""git"", - ""url"": ""http://hg.example.com/mypackage.git"" - } - ], - ""dependencies"": { - ""webkit"": ""1.2"", - ""ssl"": { - ""gnutls"": [""1.0"", ""2.0""], - ""openssl"": ""0.9.8"" - } - }, - ""implements"": [""cjs-module-0.3"", ""cjs-jsgi-0.1""], - ""os"": [""linux"", ""macos"", ""win""], - ""cpu"": [""x86"", ""ppc"", ""x86_64""], - ""engines"": [""v8"", ""ejs"", ""node"", ""rhino""], - ""scripts"": { - ""install"": ""install.js"", - ""uninstall"": ""uninstall.js"", - ""build"": ""build.js"", - ""test"": ""test.js"" - }, - ""man"" : [ ""./man/foo.1"", ""./man/bar.1"" ], - ""files"" : [""server.js"", ""customlib.js"", ""path/to/subfolder""], - ""directories"": { - ""lib"": ""src/lib"", - ""bin"": ""local/binaries"", - ""jars"": ""java"" - } -}"; - - private const string PkgLargeNonCompliant = @"{ - ""name"": ""mypackage"", - ""version"": ""0.7.0"", - ""description"": ""Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package."", - ""author"": { - ""url"": ""http://firstnamelastname.com"", - ""name"": ""Firstname Lastname"" - }, - ""keywords"": [ - ""package"", - ""example"" - ], - ""homepage"": [""http://www.mypackagehomepage.com/""], - ""maintainers"": [ - { - ""name"": ""Bill Smith"", - ""email"": ""bills@example.com"", - ""web"": ""http://www.example.com"" - } - ], - ""contributors"": [ - { - ""name"": ""Mary Brown"", - ""email"": ""maryb@embedthis.com"", - ""web"": ""http://www.embedthis.com"" - } - ], - ""bugs"": { - ""mail"": ""dev@example.com"", - ""web"": ""http://www.example.com/bugs"" - }, - ""licenses"": [ - { - ""type"": ""GPLv2"", - ""url"": ""http://www.example.org/licenses/gpl.html"" - } - ], - ""repositories"": [ - { - ""type"": ""git"", - ""url"": ""http://hg.example.com/mypackage.git"" - } - ], - ""dependencies"": { - ""webkit"": ""1.2"", - ""ssl"": { - ""gnutls"": [""1.0"", ""2.0""], - ""openssl"": ""0.9.8"" - } - }, - ""implements"": [""cjs-module-0.3"", ""cjs-jsgi-0.1""], - ""os"": [""linux"", ""macos"", ""win""], - ""cpu"": [""x86"", ""ppc"", ""x86_64""], - ""engines"": [""v8"", ""ejs"", ""node"", ""rhino""], - ""scripts"": { - ""install"": ""install.js"", - ""uninstall"": ""uninstall.js"", - ""build"": ""build.js"", - ""test"": ""test.js"" - }, - ""man"" : ""./man/foo.1"", - ""directories"": { - ""lib"": ""src/lib"", - ""bin"": ""local/binaries"", - ""jars"": ""java"" - } -}"; - - [TestMethod, Priority(0)] - public void TestReadNoNameNull() { - var pkg = LoadFrom(PkgEmpty); - Assert.IsNull(pkg.Name, "Name should be null."); - } - - [TestMethod, Priority(0)] - public void TestReadNoVersionIsZeroed() { - var pkg = LoadFrom(PkgEmpty); - Assert.AreEqual(new SemverVersion(), pkg.Version, "Empty version mismatch."); - } - - [TestMethod, Priority(0)] - public void TestReadNameAndVersion() { - var pkgJson = LoadFrom(PkgSimple); - - dynamic json = JsonConvert.DeserializeObject(PkgSimple); - - Assert.AreEqual(json.name.ToString(), pkgJson.Name, "Mismatched package names."); - Assert.AreEqual(json.version.ToString(), pkgJson.Version.ToString(), "Mismatched version strings."); - - SemverVersionTestHelper.AssertVersionsEqual(0, 1, 0, null, null, pkgJson.Version); - } - - [TestMethod, Priority(0)] - public void TestGetEmptyScripts() { - var pkg = LoadFrom(PkgSimple); - var scripts = pkg.Scripts; - Assert.IsNotNull(scripts, "Scripts collection should not be null."); - Assert.AreEqual(0, scripts.Count, "Shouldn't find any scripts."); - } - - [TestMethod, Priority(0)] - public void TestReadSingleStartScript() { - var pkg = LoadFrom(PkgStartScript); - var scripts = pkg.Scripts; - Assert.AreEqual(1, scripts.Count, "Should be a single script."); - IScript start = scripts[ScriptName.Start]; - Assert.IsNotNull(start, "Start script should not be null."); - Assert.AreEqual(ScriptName.Start, start.Name, "Script name mismatch."); - Assert.AreEqual("node server.js", start.Code, "Script code mismatch."); - } - - [TestMethod, Priority(0)] - public void TestReadNonExistentScriptsNull() { - var pkg = LoadFrom(PkgStartScript); - var scripts = pkg.Scripts; - - foreach (var name in new[]{ - ScriptName.Install, - ScriptName.Postinstall, - ScriptName.Postpublish, - ScriptName.Postrestart, - ScriptName.Poststart, - ScriptName.Poststop, - ScriptName.Posttest - }) { - Assert.IsNull(scripts[name], string.Format("Script '{0}' should be null.", name)); - } - } - - [TestMethod, Priority(0)] - public void TestReadNoDescriptionNull() { - var pkg = LoadFrom(PkgEmpty); - Assert.IsNull(pkg.Description, "Description should be null."); - } - - [TestMethod, Priority(0)] - public void TestReadDescription() { - var pkg = LoadFrom(PkgLargeCompliant); - Assert.AreEqual( - "Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package.", - pkg.Description, - "Description mismatch."); - } - - [TestMethod, Priority(0)] - public void TestReadEmptyKeywordsCountZero() { - CheckEmptyArray(LoadFrom(PkgEmpty).Keywords); - } - - [TestMethod, Priority(0)] - public void TestEnumerationOverKeywords() { - CheckStringArrayContents( - LoadFrom(PkgLargeCompliant).Keywords, - 2, - new[] { "package", "example" }); - } - - [TestMethod, Priority(0)] - public void TestReadNoHomepageEmpty() { - var pkg = LoadFrom(PkgSimple); - Assert.AreEqual(0, pkg.Homepages.Count, "Homepage should be empty."); - } - - [TestMethod, Priority(0)] - public void TestReadHomepageCompliant() { - var pkg = LoadFrom(PkgLargeCompliant); - CheckStringArrayContents( - pkg.Homepages, - 1, - new[] { "http://www.mypackagehomepage.com/" }); - } - - [TestMethod, Priority(0)] - public void TestReadHomepageNonCompliant() { - var pkg = LoadFrom(PkgLargeNonCompliant); - CheckStringArrayContents( - pkg.Homepages, - 1, - new[] { "http://www.mypackagehomepage.com/" }); - } - - [TestMethod, Priority(0)] - public void TestReadNoBugsNull() { - var pkg = LoadFrom(PkgSimple); - Assert.IsNull(pkg.Bugs, "Bugs should be null."); - } - - [TestMethod, Priority(0)] - public void TestReadBugsUrlOnly() { - var pkg = LoadFrom(PkgSimpleBugs); - var bugs = pkg.Bugs; - Assert.IsNotNull(bugs, "Bugs should not be null."); - Assert.AreEqual("http://www.mybugtracker.com/", bugs.Url, "Bugs URL mismatch."); - Assert.IsNull(bugs.Email, "Bugs email should be null."); - } - - private void TestReadBugsUrlAndEmail(string json) { - var pkg = LoadFrom(json); - var bugs = pkg.Bugs; - Assert.IsNotNull(bugs, "Bugs should not be null."); - Assert.AreEqual("http://www.example.com/bugs", bugs.Url, "Bugs URL mismatch."); - Assert.AreEqual("dev@example.com", bugs.Email, "Bugs email mismatch."); - } - - [TestMethod, Priority(0)] - public void TestReadBugsUrlAndEmailCompliant() { - TestReadBugsUrlAndEmail(PkgLargeCompliant); - } - - [TestMethod, Priority(0)] - public void TestReadBugsUrlAndEmailNonCompliant() { - TestReadBugsUrlAndEmail(PkgLargeNonCompliant); - } - - [TestMethod, Priority(0)] - public void TestReadNoLicensesEmpty() { - var pkg = LoadFrom(PkgSimpleBugs); - var licenses = pkg.Licenses; - Assert.IsNotNull(licenses, "Licenses should not be null."); - Assert.AreEqual(0, licenses.Count, "Should not be any licenses."); - } - - [TestMethod, Priority(0)] - public void TestReadLicensesTypeOnly() { - var pkg = LoadFrom(PkgSingleLicenseType); - var licenses = pkg.Licenses; - Assert.AreEqual(1, licenses.Count, "License count mismatch."); - var license = licenses[0]; - Assert.IsNotNull(license, "License should not be null."); - Assert.AreEqual("BSD", license.Type, "License type mismatch."); - } - - [TestMethod, Priority(0)] - public void ReadLicensesTypeAndUrl() { - var pkg = LoadFrom(PkgLargeCompliant); - var licenses = pkg.Licenses; - Assert.AreEqual(1, licenses.Count, "License count mismatch."); - var license = licenses[0]; - Assert.IsNotNull(license, "License should not be null."); - Assert.AreEqual("GPLv2", license.Type, "License type mismatch."); - Assert.AreEqual("http://www.example.org/licenses/gpl.html", license.Url, "License URL mismatch."); - } - - [TestMethod, Priority(0)] - public void TestReadEmptyFilesEmpty() { - CheckEmptyArray(LoadFrom(PkgSimple).Files); - } - - [TestMethod, Priority(0)] - public void TestReadFiles() { - CheckStringArrayContents( - LoadFrom(PkgLargeCompliant).Files, - 3, - new[] { "server.js", "customlib.js", "path/to/subfolder" }); - } - - [TestMethod, Priority(0)] - public void TestReadEmptyManEmpty() { - CheckEmptyArray(LoadFrom(PkgSimple).Man); - } - - [TestMethod, Priority(0)] - public void TestReadSingleMan() { - CheckStringArrayContents( - LoadFrom(PkgLargeNonCompliant).Man, - 1, - new[] { "./man/foo.1" }); - } - - [TestMethod, Priority(0)] - public void TestReadMultiMan() { - CheckStringArrayContents( - LoadFrom(PkgLargeCompliant).Man, - 2, - new[] { "./man/foo.1", "./man/bar.1" }); - } - - [TestMethod, Priority(0)] - public void TestReadEmptyAuthor() { - Assert.IsNull(LoadFrom(PkgSimple).Author); - } - - [TestMethod, Priority(0)] - public void TestReadMisspelledAuthor() { - Assert.AreEqual( - @"{ - ""misspelledname"": ""Firstname Lastname"" -}", - LoadFrom(PkgMisSpelledAuthor).Author.Name - ); - } - - [TestMethod, Priority(0)] - public void TestReadSingleAuthorField() { - Assert.AreEqual( - "Firstname Lastname", - LoadFrom(PkgSingleAuthorField).Author.Name - ); - } - - [TestMethod, Priority(0)] - public void TestReadAuthorCompliant() { - var compliantAuthor = LoadFrom(PkgLargeCompliant).Author; - Assert.AreEqual("Firstname Lastname", compliantAuthor.Name); - Assert.AreEqual("firstname@lastname.com", compliantAuthor.Email); - Assert.AreEqual("http://firstnamelastname.com", compliantAuthor.Url); - } - - [TestMethod, Priority(0)] - public void TestReadAuthorNonCompliant() { - var nonCompliantAuthor = LoadFrom(PkgLargeNonCompliant).Author; - Assert.AreEqual("Firstname Lastname", nonCompliantAuthor.Name); - Assert.AreEqual("http://firstnamelastname.com", nonCompliantAuthor.Url); - Assert.IsNull(nonCompliantAuthor.Email); - } - - // TODO: authors, contributors, private, main, bin, directories (hash), repository, config, - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Newtonsoft.Json; + +namespace NpmTests { + [TestClass] + public class PackageJsonTests : AbstractPackageJsonTests { + private const string PkgSimpleBugs = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""bugs"": ""http://www.mybugtracker.com/"" +}"; + + private const string PkgMisSpelledAuthor = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""author"": { + ""misspelledname"": ""Firstname Lastname"" + } +}"; + + private const string PkgSingleAuthorField = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""author"": { + ""name"": ""Firstname Lastname"" + } +}"; + + private const string PkgSingleLicenseType = @"{ + ""name"": ""TestPkg"", + ""version"": ""0.1.0"", + ""license"" : ""BSD"" +}"; + + private const string PkgStartScript = @"{ + ""name"": ""ScriptPkg"", + ""version"": ""1.2.3"", + ""scripts"": {""start"": ""node server.js""} +}"; + + private const string PkgLargeCompliant = @"{ + ""name"": ""mypackage"", + ""version"": ""0.7.0"", + ""description"": ""Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package."", + ""author"": { + ""name"": ""Firstname Lastname"", + ""email"": ""firstname@lastname.com"", + ""url"": ""http://firstnamelastname.com"" + }, + ""keywords"": [ + ""package"", + ""example"" + ], + ""homepage"": ""http://www.mypackagehomepage.com/"", + ""maintainers"": [ + { + ""name"": ""Bill Smith"", + ""email"": ""bills@example.com"", + ""web"": ""http://www.example.com"" + } + ], + ""contributors"": [ + { + ""name"": ""Mary Brown"", + ""email"": ""maryb@embedthis.com"", + ""web"": ""http://www.embedthis.com"" + } + ], + ""bugs"": { + ""email"": ""dev@example.com"", + ""url"": ""http://www.example.com/bugs"" + }, + ""licenses"": [ + { + ""type"": ""GPLv2"", + ""url"": ""http://www.example.org/licenses/gpl.html"" + } + ], + ""repositories"": [ + { + ""type"": ""git"", + ""url"": ""http://hg.example.com/mypackage.git"" + } + ], + ""dependencies"": { + ""webkit"": ""1.2"", + ""ssl"": { + ""gnutls"": [""1.0"", ""2.0""], + ""openssl"": ""0.9.8"" + } + }, + ""implements"": [""cjs-module-0.3"", ""cjs-jsgi-0.1""], + ""os"": [""linux"", ""macos"", ""win""], + ""cpu"": [""x86"", ""ppc"", ""x86_64""], + ""engines"": [""v8"", ""ejs"", ""node"", ""rhino""], + ""scripts"": { + ""install"": ""install.js"", + ""uninstall"": ""uninstall.js"", + ""build"": ""build.js"", + ""test"": ""test.js"" + }, + ""man"" : [ ""./man/foo.1"", ""./man/bar.1"" ], + ""files"" : [""server.js"", ""customlib.js"", ""path/to/subfolder""], + ""directories"": { + ""lib"": ""src/lib"", + ""bin"": ""local/binaries"", + ""jars"": ""java"" + } +}"; + + private const string PkgLargeNonCompliant = @"{ + ""name"": ""mypackage"", + ""version"": ""0.7.0"", + ""description"": ""Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package."", + ""author"": { + ""url"": ""http://firstnamelastname.com"", + ""name"": ""Firstname Lastname"" + }, + ""keywords"": [ + ""package"", + ""example"" + ], + ""homepage"": [""http://www.mypackagehomepage.com/""], + ""maintainers"": [ + { + ""name"": ""Bill Smith"", + ""email"": ""bills@example.com"", + ""web"": ""http://www.example.com"" + } + ], + ""contributors"": [ + { + ""name"": ""Mary Brown"", + ""email"": ""maryb@embedthis.com"", + ""web"": ""http://www.embedthis.com"" + } + ], + ""bugs"": { + ""mail"": ""dev@example.com"", + ""web"": ""http://www.example.com/bugs"" + }, + ""licenses"": [ + { + ""type"": ""GPLv2"", + ""url"": ""http://www.example.org/licenses/gpl.html"" + } + ], + ""repositories"": [ + { + ""type"": ""git"", + ""url"": ""http://hg.example.com/mypackage.git"" + } + ], + ""dependencies"": { + ""webkit"": ""1.2"", + ""ssl"": { + ""gnutls"": [""1.0"", ""2.0""], + ""openssl"": ""0.9.8"" + } + }, + ""implements"": [""cjs-module-0.3"", ""cjs-jsgi-0.1""], + ""os"": [""linux"", ""macos"", ""win""], + ""cpu"": [""x86"", ""ppc"", ""x86_64""], + ""engines"": [""v8"", ""ejs"", ""node"", ""rhino""], + ""scripts"": { + ""install"": ""install.js"", + ""uninstall"": ""uninstall.js"", + ""build"": ""build.js"", + ""test"": ""test.js"" + }, + ""man"" : ""./man/foo.1"", + ""directories"": { + ""lib"": ""src/lib"", + ""bin"": ""local/binaries"", + ""jars"": ""java"" + } +}"; + + [TestMethod, Priority(0)] + public void TestReadNoNameNull() { + var pkg = LoadFrom(PkgEmpty); + Assert.IsNull(pkg.Name, "Name should be null."); + } + + [TestMethod, Priority(0)] + public void TestReadNoVersionIsZeroed() { + var pkg = LoadFrom(PkgEmpty); + Assert.AreEqual(new SemverVersion(), pkg.Version, "Empty version mismatch."); + } + + [TestMethod, Priority(0)] + public void TestReadNameAndVersion() { + var pkgJson = LoadFrom(PkgSimple); + + dynamic json = JsonConvert.DeserializeObject(PkgSimple); + + Assert.AreEqual(json.name.ToString(), pkgJson.Name, "Mismatched package names."); + Assert.AreEqual(json.version.ToString(), pkgJson.Version.ToString(), "Mismatched version strings."); + + SemverVersionTestHelper.AssertVersionsEqual(0, 1, 0, null, null, pkgJson.Version); + } + + [TestMethod, Priority(0)] + public void TestGetEmptyScripts() { + var pkg = LoadFrom(PkgSimple); + var scripts = pkg.Scripts; + Assert.IsNotNull(scripts, "Scripts collection should not be null."); + Assert.AreEqual(0, scripts.Count, "Shouldn't find any scripts."); + } + + [TestMethod, Priority(0)] + public void TestReadSingleStartScript() { + var pkg = LoadFrom(PkgStartScript); + var scripts = pkg.Scripts; + Assert.AreEqual(1, scripts.Count, "Should be a single script."); + IScript start = scripts[ScriptName.Start]; + Assert.IsNotNull(start, "Start script should not be null."); + Assert.AreEqual(ScriptName.Start, start.Name, "Script name mismatch."); + Assert.AreEqual("node server.js", start.Code, "Script code mismatch."); + } + + [TestMethod, Priority(0)] + public void TestReadNonExistentScriptsNull() { + var pkg = LoadFrom(PkgStartScript); + var scripts = pkg.Scripts; + + foreach (var name in new[]{ + ScriptName.Install, + ScriptName.Postinstall, + ScriptName.Postpublish, + ScriptName.Postrestart, + ScriptName.Poststart, + ScriptName.Poststop, + ScriptName.Posttest + }) { + Assert.IsNull(scripts[name], string.Format("Script '{0}' should be null.", name)); + } + } + + [TestMethod, Priority(0)] + public void TestReadNoDescriptionNull() { + var pkg = LoadFrom(PkgEmpty); + Assert.IsNull(pkg.Description, "Description should be null."); + } + + [TestMethod, Priority(0)] + public void TestReadDescription() { + var pkg = LoadFrom(PkgLargeCompliant); + Assert.AreEqual( + "Sample package for CommonJS. This package demonstrates the required elements of a CommonJS package.", + pkg.Description, + "Description mismatch."); + } + + [TestMethod, Priority(0)] + public void TestReadEmptyKeywordsCountZero() { + CheckEmptyArray(LoadFrom(PkgEmpty).Keywords); + } + + [TestMethod, Priority(0)] + public void TestEnumerationOverKeywords() { + CheckStringArrayContents( + LoadFrom(PkgLargeCompliant).Keywords, + 2, + new[] { "package", "example" }); + } + + [TestMethod, Priority(0)] + public void TestReadNoHomepageEmpty() { + var pkg = LoadFrom(PkgSimple); + Assert.AreEqual(0, pkg.Homepages.Count, "Homepage should be empty."); + } + + [TestMethod, Priority(0)] + public void TestReadHomepageCompliant() { + var pkg = LoadFrom(PkgLargeCompliant); + CheckStringArrayContents( + pkg.Homepages, + 1, + new[] { "http://www.mypackagehomepage.com/" }); + } + + [TestMethod, Priority(0)] + public void TestReadHomepageNonCompliant() { + var pkg = LoadFrom(PkgLargeNonCompliant); + CheckStringArrayContents( + pkg.Homepages, + 1, + new[] { "http://www.mypackagehomepage.com/" }); + } + + [TestMethod, Priority(0)] + public void TestReadNoBugsNull() { + var pkg = LoadFrom(PkgSimple); + Assert.IsNull(pkg.Bugs, "Bugs should be null."); + } + + [TestMethod, Priority(0)] + public void TestReadBugsUrlOnly() { + var pkg = LoadFrom(PkgSimpleBugs); + var bugs = pkg.Bugs; + Assert.IsNotNull(bugs, "Bugs should not be null."); + Assert.AreEqual("http://www.mybugtracker.com/", bugs.Url, "Bugs URL mismatch."); + Assert.IsNull(bugs.Email, "Bugs email should be null."); + } + + private void TestReadBugsUrlAndEmail(string json) { + var pkg = LoadFrom(json); + var bugs = pkg.Bugs; + Assert.IsNotNull(bugs, "Bugs should not be null."); + Assert.AreEqual("http://www.example.com/bugs", bugs.Url, "Bugs URL mismatch."); + Assert.AreEqual("dev@example.com", bugs.Email, "Bugs email mismatch."); + } + + [TestMethod, Priority(0)] + public void TestReadBugsUrlAndEmailCompliant() { + TestReadBugsUrlAndEmail(PkgLargeCompliant); + } + + [TestMethod, Priority(0)] + public void TestReadBugsUrlAndEmailNonCompliant() { + TestReadBugsUrlAndEmail(PkgLargeNonCompliant); + } + + [TestMethod, Priority(0)] + public void TestReadNoLicensesEmpty() { + var pkg = LoadFrom(PkgSimpleBugs); + var licenses = pkg.Licenses; + Assert.IsNotNull(licenses, "Licenses should not be null."); + Assert.AreEqual(0, licenses.Count, "Should not be any licenses."); + } + + [TestMethod, Priority(0)] + public void TestReadLicensesTypeOnly() { + var pkg = LoadFrom(PkgSingleLicenseType); + var licenses = pkg.Licenses; + Assert.AreEqual(1, licenses.Count, "License count mismatch."); + var license = licenses[0]; + Assert.IsNotNull(license, "License should not be null."); + Assert.AreEqual("BSD", license.Type, "License type mismatch."); + } + + [TestMethod, Priority(0)] + public void ReadLicensesTypeAndUrl() { + var pkg = LoadFrom(PkgLargeCompliant); + var licenses = pkg.Licenses; + Assert.AreEqual(1, licenses.Count, "License count mismatch."); + var license = licenses[0]; + Assert.IsNotNull(license, "License should not be null."); + Assert.AreEqual("GPLv2", license.Type, "License type mismatch."); + Assert.AreEqual("http://www.example.org/licenses/gpl.html", license.Url, "License URL mismatch."); + } + + [TestMethod, Priority(0)] + public void TestReadEmptyFilesEmpty() { + CheckEmptyArray(LoadFrom(PkgSimple).Files); + } + + [TestMethod, Priority(0)] + public void TestReadFiles() { + CheckStringArrayContents( + LoadFrom(PkgLargeCompliant).Files, + 3, + new[] { "server.js", "customlib.js", "path/to/subfolder" }); + } + + [TestMethod, Priority(0)] + public void TestReadEmptyManEmpty() { + CheckEmptyArray(LoadFrom(PkgSimple).Man); + } + + [TestMethod, Priority(0)] + public void TestReadSingleMan() { + CheckStringArrayContents( + LoadFrom(PkgLargeNonCompliant).Man, + 1, + new[] { "./man/foo.1" }); + } + + [TestMethod, Priority(0)] + public void TestReadMultiMan() { + CheckStringArrayContents( + LoadFrom(PkgLargeCompliant).Man, + 2, + new[] { "./man/foo.1", "./man/bar.1" }); + } + + [TestMethod, Priority(0)] + public void TestReadEmptyAuthor() { + Assert.IsNull(LoadFrom(PkgSimple).Author); + } + + [TestMethod, Priority(0)] + public void TestReadMisspelledAuthor() { + Assert.AreEqual( + @"{ + ""misspelledname"": ""Firstname Lastname"" +}", + LoadFrom(PkgMisSpelledAuthor).Author.Name + ); + } + + [TestMethod, Priority(0)] + public void TestReadSingleAuthorField() { + Assert.AreEqual( + "Firstname Lastname", + LoadFrom(PkgSingleAuthorField).Author.Name + ); + } + + [TestMethod, Priority(0)] + public void TestReadAuthorCompliant() { + var compliantAuthor = LoadFrom(PkgLargeCompliant).Author; + Assert.AreEqual("Firstname Lastname", compliantAuthor.Name); + Assert.AreEqual("firstname@lastname.com", compliantAuthor.Email); + Assert.AreEqual("http://firstnamelastname.com", compliantAuthor.Url); + } + + [TestMethod, Priority(0)] + public void TestReadAuthorNonCompliant() { + var nonCompliantAuthor = LoadFrom(PkgLargeNonCompliant).Author; + Assert.AreEqual("Firstname Lastname", nonCompliantAuthor.Name); + Assert.AreEqual("http://firstnamelastname.com", nonCompliantAuthor.Url); + Assert.IsNull(nonCompliantAuthor.Email); + } + + // TODO: authors, contributors, private, main, bin, directories (hash), repository, config, + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/ProblematicPackageJsonTests.cs b/Nodejs/Tests/NpmTests/ProblematicPackageJsonTests.cs index ca9f8b000..5c3e45319 100644 --- a/Nodejs/Tests/NpmTests/ProblematicPackageJsonTests.cs +++ b/Nodejs/Tests/NpmTests/ProblematicPackageJsonTests.cs @@ -1,263 +1,263 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - [TestClass] - [DeploymentItem(@"TestData\NpmPackageJsonData\", "NpmPackageJsonData")] - public class ProblematicPackageJsonTests : AbstractPackageJsonTests { - - [TestMethod, Priority(0)] - public void TestFreshPackageJsonParseFromResource() { - var pkg = LoadFromResource("NpmTests.TestData.fresh_package.json"); - Assert.IsNotNull(pkg, "Fresh package should not be null."); - } - - private void TestParseFromFile(string filename) { - string file = string.Format(@"NpmPackageJsonData\{0}", filename); - var pkg = LoadFromFile(file); - Assert.IsNotNull( - pkg, - string.Format("Package from file '{0}' should not be null.", file)); - } - - [TestMethod, Priority(0)] - public void TestFreshPackageParseFromFile() { - TestParseFromFile("fresh_package.json"); - } - - private void TestFreshPackage(string suffix) { - TestParseFromFile(string.Format("fresh_package_{0}.json", suffix)); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseDoubleColon() { - TestFreshPackage("doublecolon"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseDoubleComma() { - TestFreshPackage("doublecomma"); - } - - [Ignore] - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseDuplicateProperty() { - TestFreshPackage("duplicateproperty"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseLeadingBrace() { - TestFreshPackage("leadingbrace"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseLeadingLetter() { - TestFreshPackage("leadingletter"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseLeadingSquareBrace() { - TestFreshPackage("leadingsquarebrace"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseMissingColon() { - TestFreshPackage("missingcolon"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseMissingComma() { - TestFreshPackage("missingcomma"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseMissingLeadingBrace() { - TestFreshPackage("missingleadingbrace"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseMissingLeadingListBrace() { - TestFreshPackage("missingleadinglistbrace"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseMissingLeadingPropNameQuote() { - TestFreshPackage("missingleadingpropnamequote"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseMissingOnePropNameQuote() { - TestFreshPackage("missingonepropnamequote"); - } - - [TestMethod, Priority(0)] - public void TestParseMissingPropNameQuotes() { - TestFreshPackage("missingpropnamequotes"); - } - - [Ignore] - [TestMethod, Priority(0)] - public void TestParseMissingTrailingBrace() { - TestFreshPackage("missingtrailingbrace"); - } - - [Ignore] - [TestMethod, Priority(0)] - public void TestParseMissingTrailingListBrace() { - TestFreshPackage("missingtrailinglistbrace"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(PackageJsonException))] - public void TestParseUnescapedQuote() { - TestFreshPackage("unescapedquote"); - } - - [TestMethod, Priority(0)] - public void TestParseCppStyleComment_WorkItem563() { - var buff = new StringBuilder(@"{ - ""name"": ""angular-app-server"", - ""description"": ""Back end server to support our angular app"", - ""version"": ""0.0.1"", - ""private"": true, - ""dependencies"": { - ""express"": ""~3.0"", - ""passport"": ""~0.1.12"", - //""passport-local"": ""~0.1.6"", - ""express-namespace"": ""~0.1.1"", - ""open"": ""0.0.3"", - ""request"": ""~2.16.6"" - }, - ""devDependencies"": { - ""rewire"": ""~1.0.3"", - ""supervisor"": ""~0.4.1"", - ""grunt"": ""~0.4"", - ""grunt-contrib-jshint"": ""~0.2.0"", - ""grunt-contrib-nodeunit"": ""~0.1.2"" - } -} -"); - ParseFromBuff(buff); - } - - private void ParseFromBuff(StringBuilder buff) { - try { - using (var reader = new StringReader(buff.ToString())) { - LoadFrom(reader); - } - } catch (PackageJsonException) { - // This is fine -> do nothing - } - } - - [TestMethod, Priority(0)] - public void TestParseFromEveryCharValue() { - var buff = new StringBuilder(); - var ch = (char)0; - do { - buff.Append(ch); - - ParseFromBuff(buff); - - buff.Length = 0; - ++ch; - } while (ch != 0); - } - - [TestMethod, Priority(0)] - public void VeryEvilRandom1CharCorruptionTest() { - var original = LoadStringFromResource("NpmTests.TestData.fresh_package.json"); - var buff = new StringBuilder(); - var generator = new Random(); - - var ch = (char)0; - var index = 0; - - try { - do { - buff.Append(original); - index = generator.Next(buff.Length); - buff[index] = ch; - - ParseFromBuff(buff); - - buff.Length = 0; - ++ch; - } while (ch != 0); - } catch (Exception ex) { - throw new PackageJsonException( - string.Format(@"Corruption/replacement test failed replacing with character code {0:x} at index {1} in content: - -{2} - -Exception message: {3}", (int)ch, index, buff, ex.Message), - ex); - } - } - - [TestMethod, Priority(0)] - public void VeryEvilRandom1CharInsertionTest() { - var original = LoadStringFromResource("NpmTests.TestData.fresh_package.json"); - var buff = new StringBuilder(); - var generator = new Random(); - - var ch = (char)0; - var index = 0; - - try { - do { - buff.Append(original); - index = generator.Next(buff.Length); - buff.Insert(index, ch); - - ParseFromBuff(buff); - - buff.Length = 0; - ++ch; - } while (ch != 0); - } catch (Exception ex) { - throw new PackageJsonException( - string.Format(@"Insertion test failed inserting character code {0:x} at index {1} in content: - -{2} - -Exception message: {3}", (int)ch, index, buff, ex.Message), - ex); - } - } - } -} +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + [TestClass] + [DeploymentItem(@"TestData\NpmPackageJsonData\", "NpmPackageJsonData")] + public class ProblematicPackageJsonTests : AbstractPackageJsonTests { + + [TestMethod, Priority(0)] + public void TestFreshPackageJsonParseFromResource() { + var pkg = LoadFromResource("NpmTests.TestData.fresh_package.json"); + Assert.IsNotNull(pkg, "Fresh package should not be null."); + } + + private void TestParseFromFile(string filename) { + string file = string.Format(@"NpmPackageJsonData\{0}", filename); + var pkg = LoadFromFile(file); + Assert.IsNotNull( + pkg, + string.Format("Package from file '{0}' should not be null.", file)); + } + + [TestMethod, Priority(0)] + public void TestFreshPackageParseFromFile() { + TestParseFromFile("fresh_package.json"); + } + + private void TestFreshPackage(string suffix) { + TestParseFromFile(string.Format("fresh_package_{0}.json", suffix)); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseDoubleColon() { + TestFreshPackage("doublecolon"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseDoubleComma() { + TestFreshPackage("doublecomma"); + } + + [Ignore] + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseDuplicateProperty() { + TestFreshPackage("duplicateproperty"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseLeadingBrace() { + TestFreshPackage("leadingbrace"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseLeadingLetter() { + TestFreshPackage("leadingletter"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseLeadingSquareBrace() { + TestFreshPackage("leadingsquarebrace"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseMissingColon() { + TestFreshPackage("missingcolon"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseMissingComma() { + TestFreshPackage("missingcomma"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseMissingLeadingBrace() { + TestFreshPackage("missingleadingbrace"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseMissingLeadingListBrace() { + TestFreshPackage("missingleadinglistbrace"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseMissingLeadingPropNameQuote() { + TestFreshPackage("missingleadingpropnamequote"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseMissingOnePropNameQuote() { + TestFreshPackage("missingonepropnamequote"); + } + + [TestMethod, Priority(0)] + public void TestParseMissingPropNameQuotes() { + TestFreshPackage("missingpropnamequotes"); + } + + [Ignore] + [TestMethod, Priority(0)] + public void TestParseMissingTrailingBrace() { + TestFreshPackage("missingtrailingbrace"); + } + + [Ignore] + [TestMethod, Priority(0)] + public void TestParseMissingTrailingListBrace() { + TestFreshPackage("missingtrailinglistbrace"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(PackageJsonException))] + public void TestParseUnescapedQuote() { + TestFreshPackage("unescapedquote"); + } + + [TestMethod, Priority(0)] + public void TestParseCppStyleComment_WorkItem563() { + var buff = new StringBuilder(@"{ + ""name"": ""angular-app-server"", + ""description"": ""Back end server to support our angular app"", + ""version"": ""0.0.1"", + ""private"": true, + ""dependencies"": { + ""express"": ""~3.0"", + ""passport"": ""~0.1.12"", + //""passport-local"": ""~0.1.6"", + ""express-namespace"": ""~0.1.1"", + ""open"": ""0.0.3"", + ""request"": ""~2.16.6"" + }, + ""devDependencies"": { + ""rewire"": ""~1.0.3"", + ""supervisor"": ""~0.4.1"", + ""grunt"": ""~0.4"", + ""grunt-contrib-jshint"": ""~0.2.0"", + ""grunt-contrib-nodeunit"": ""~0.1.2"" + } +} +"); + ParseFromBuff(buff); + } + + private void ParseFromBuff(StringBuilder buff) { + try { + using (var reader = new StringReader(buff.ToString())) { + LoadFrom(reader); + } + } catch (PackageJsonException) { + // This is fine -> do nothing + } + } + + [TestMethod, Priority(0)] + public void TestParseFromEveryCharValue() { + var buff = new StringBuilder(); + var ch = (char)0; + do { + buff.Append(ch); + + ParseFromBuff(buff); + + buff.Length = 0; + ++ch; + } while (ch != 0); + } + + [TestMethod, Priority(0)] + public void VeryEvilRandom1CharCorruptionTest() { + var original = LoadStringFromResource("NpmTests.TestData.fresh_package.json"); + var buff = new StringBuilder(); + var generator = new Random(); + + var ch = (char)0; + var index = 0; + + try { + do { + buff.Append(original); + index = generator.Next(buff.Length); + buff[index] = ch; + + ParseFromBuff(buff); + + buff.Length = 0; + ++ch; + } while (ch != 0); + } catch (Exception ex) { + throw new PackageJsonException( + string.Format(@"Corruption/replacement test failed replacing with character code {0:x} at index {1} in content: + +{2} + +Exception message: {3}", (int)ch, index, buff, ex.Message), + ex); + } + } + + [TestMethod, Priority(0)] + public void VeryEvilRandom1CharInsertionTest() { + var original = LoadStringFromResource("NpmTests.TestData.fresh_package.json"); + var buff = new StringBuilder(); + var generator = new Random(); + + var ch = (char)0; + var index = 0; + + try { + do { + buff.Append(original); + index = generator.Next(buff.Length); + buff.Insert(index, ch); + + ParseFromBuff(buff); + + buff.Length = 0; + ++ch; + } while (ch != 0); + } catch (Exception ex) { + throw new PackageJsonException( + string.Format(@"Insertion test failed inserting character code {0:x} at index {1} in content: + +{2} + +Exception message: {3}", (int)ch, index, buff, ex.Message), + ex); + } + } + } +} diff --git a/Nodejs/Tests/NpmTests/Properties/AssemblyInfo.cs b/Nodejs/Tests/NpmTests/Properties/AssemblyInfo.cs index 4c42fbdb8..f8f9d61da 100644 --- a/Nodejs/Tests/NpmTests/Properties/AssemblyInfo.cs +++ b/Nodejs/Tests/NpmTests/Properties/AssemblyInfo.cs @@ -1,18 +1,18 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("NpmTests")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("87cf3ddc-6e8e-43b3-8e20-adf9d5aba23e")] - +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("NpmTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("87cf3ddc-6e8e-43b3-8e20-adf9d5aba23e")] + diff --git a/Nodejs/Tests/NpmTests/SemverVersionTestHelper.cs b/Nodejs/Tests/NpmTests/SemverVersionTestHelper.cs index b3d2c7eda..0f21fc017 100644 --- a/Nodejs/Tests/NpmTests/SemverVersionTestHelper.cs +++ b/Nodejs/Tests/NpmTests/SemverVersionTestHelper.cs @@ -1,55 +1,55 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System.Text; -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - internal static class SemverVersionTestHelper { - public static void AssertVersionsEqual( - ulong expectedMajor, - ulong expectedMinor, - ulong expectedPatch, - string expectedPreRelease, - string expectedBuildMetadata, - SemverVersion actual) { - Assert.AreEqual(expectedMajor, actual.Major); - Assert.AreEqual(expectedMinor, actual.Minor, "Mismatched minor version."); - Assert.AreEqual(expectedPatch, actual.Patch, "Mismatched patch version."); - Assert.AreEqual( - null != expectedPreRelease, - actual.HasPreReleaseVersion, - "Pre-release version info presence mismatch."); - Assert.AreEqual(expectedPreRelease, actual.PreReleaseVersion, "Pre-release version info mismatch."); - Assert.AreEqual(null != expectedBuildMetadata, actual.HasBuildMetadata, "Build metadata presence mismatch."); - Assert.AreEqual(expectedBuildMetadata, actual.BuildMetadata, "Build metadata mismatch."); - - var expected = new StringBuilder(string.Format("{0}.{1}.{2}", expectedMajor, expectedMinor, expectedPatch)); - if (null != expectedPreRelease) { - expected.Append('-'); - expected.Append(expectedPreRelease); - } - - if (null != expectedBuildMetadata) { - expected.Append('+'); - expected.Append(expectedBuildMetadata); - } - - Assert.AreEqual(expected.ToString(), actual.ToString()); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System.Text; +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + internal static class SemverVersionTestHelper { + public static void AssertVersionsEqual( + ulong expectedMajor, + ulong expectedMinor, + ulong expectedPatch, + string expectedPreRelease, + string expectedBuildMetadata, + SemverVersion actual) { + Assert.AreEqual(expectedMajor, actual.Major); + Assert.AreEqual(expectedMinor, actual.Minor, "Mismatched minor version."); + Assert.AreEqual(expectedPatch, actual.Patch, "Mismatched patch version."); + Assert.AreEqual( + null != expectedPreRelease, + actual.HasPreReleaseVersion, + "Pre-release version info presence mismatch."); + Assert.AreEqual(expectedPreRelease, actual.PreReleaseVersion, "Pre-release version info mismatch."); + Assert.AreEqual(null != expectedBuildMetadata, actual.HasBuildMetadata, "Build metadata presence mismatch."); + Assert.AreEqual(expectedBuildMetadata, actual.BuildMetadata, "Build metadata mismatch."); + + var expected = new StringBuilder(string.Format("{0}.{1}.{2}", expectedMajor, expectedMinor, expectedPatch)); + if (null != expectedPreRelease) { + expected.Append('-'); + expected.Append(expectedPreRelease); + } + + if (null != expectedBuildMetadata) { + expected.Append('+'); + expected.Append(expectedBuildMetadata); + } + + Assert.AreEqual(expected.ToString(), actual.ToString()); + } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/SemverVersionTests.cs b/Nodejs/Tests/NpmTests/SemverVersionTests.cs index dd9c2f65e..6d50c2772 100644 --- a/Nodejs/Tests/NpmTests/SemverVersionTests.cs +++ b/Nodejs/Tests/NpmTests/SemverVersionTests.cs @@ -1,134 +1,134 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using Microsoft.NodejsTools.Npm; -using Microsoft.VisualStudio.TestTools.UnitTesting; - -namespace NpmTests { - /// - /// Tests for semantic version parsing/output. Only exists because users may - /// need to specify a pre-release version or build metadata when authoring their - /// own packages. - /// - [TestClass] - public class SemverVersionTests { - [TestMethod, Priority(0)] - public void TestBasicMajorMinorPatchVersion() { - SemverVersionTestHelper.AssertVersionsEqual( - 1, - 2, - 3, - null, - null, - SemverVersion.Parse("1.2.3")); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(SemverVersionFormatException), "Should not allow negative major version.")] - public void TestNegativeMajorVersionFails() { - SemverVersion.Parse("-1.2.3"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(SemverVersionFormatException), "Should not allow negative minor version.")] - public void TestNegativeMinorVersionFails() { - SemverVersion.Parse("1.-2.3"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(SemverVersionFormatException), "Should not allow negative patch version.")] - public void TestNegativePatchVersionFails() { - SemverVersion.Parse("1.2.-3"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(SemverVersionFormatException), "Should not allow non-numeric major version.")] - public void TestNonNumericMajorVersionFails() { - SemverVersion.Parse("a.2.3"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(SemverVersionFormatException), "Should not allow non-numeric minor version.")] - public void TestNonNumericMinorVersionFails() { - SemverVersion.Parse("1.b.3"); - } - - [TestMethod, Priority(0)] - [ExpectedException(typeof(SemverVersionFormatException), "Should not allow non-numeric patch version.")] - public void TestNonNumericPatchVersionFails() { - SemverVersion.Parse("1.2.c"); - } - - [TestMethod, Priority(0)] - public void TestAlphaPreRelease() { - SemverVersionTestHelper.AssertVersionsEqual(1, 2, 3, "alpha", null, SemverVersion.Parse("1.2.3-alpha")); - } - - [TestMethod, Priority(0)] - public void TestNumericPreRelease() { - SemverVersionTestHelper.AssertVersionsEqual(1, 2, 3, "4.5.6", null, SemverVersion.Parse("1.2.3-4.5.6")); - } - - [TestMethod, Priority(0)] - public void TestPreReleaseHyphenatedIdentifier() { - SemverVersionTestHelper.AssertVersionsEqual( - 1, - 2, - 3, - "alpha-2.1", - null, - SemverVersion.Parse("1.2.3-alpha-2.1")); - } - - [TestMethod, Priority(0)] - public void TestPreReleaseHyphenatedIdentifierWithoutVersion() - { - // This version code is crazy, and for that reason it fail when - // regional settings is set to Turkish. - SemverVersionTestHelper.AssertVersionsEqual( - 0, - 1, - 4, - "DEPRECATED-USE-cfenv-INSTEAD", - null, - SemverVersion.Parse("0.1.4-DEPRECATED-USE-cfenv-INSTEAD")); - } - - [TestMethod, Priority(0)] - public void TestPreReleaseAndBuildMetadata() { - // 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85 - SemverVersionTestHelper.AssertVersionsEqual(1, 0, 0, "alpha", "001", SemverVersion.Parse("1.0.0-alpha+001")); - SemverVersionTestHelper.AssertVersionsEqual( - 1, - 0, - 0, - "beta", - "exp.sha.5114f85", - SemverVersion.Parse("1.0.0-beta+exp.sha.5114f85")); - } - - [TestMethod, Priority(0)] - public void TestBuildMetadataOnly() { - SemverVersionTestHelper.AssertVersionsEqual( - 1, - 0, - 0, - null, - "20130313144700", - SemverVersion.Parse("1.0.0+20130313144700")); - } - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using Microsoft.NodejsTools.Npm; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace NpmTests { + /// + /// Tests for semantic version parsing/output. Only exists because users may + /// need to specify a pre-release version or build metadata when authoring their + /// own packages. + /// + [TestClass] + public class SemverVersionTests { + [TestMethod, Priority(0)] + public void TestBasicMajorMinorPatchVersion() { + SemverVersionTestHelper.AssertVersionsEqual( + 1, + 2, + 3, + null, + null, + SemverVersion.Parse("1.2.3")); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(SemverVersionFormatException), "Should not allow negative major version.")] + public void TestNegativeMajorVersionFails() { + SemverVersion.Parse("-1.2.3"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(SemverVersionFormatException), "Should not allow negative minor version.")] + public void TestNegativeMinorVersionFails() { + SemverVersion.Parse("1.-2.3"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(SemverVersionFormatException), "Should not allow negative patch version.")] + public void TestNegativePatchVersionFails() { + SemverVersion.Parse("1.2.-3"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(SemverVersionFormatException), "Should not allow non-numeric major version.")] + public void TestNonNumericMajorVersionFails() { + SemverVersion.Parse("a.2.3"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(SemverVersionFormatException), "Should not allow non-numeric minor version.")] + public void TestNonNumericMinorVersionFails() { + SemverVersion.Parse("1.b.3"); + } + + [TestMethod, Priority(0)] + [ExpectedException(typeof(SemverVersionFormatException), "Should not allow non-numeric patch version.")] + public void TestNonNumericPatchVersionFails() { + SemverVersion.Parse("1.2.c"); + } + + [TestMethod, Priority(0)] + public void TestAlphaPreRelease() { + SemverVersionTestHelper.AssertVersionsEqual(1, 2, 3, "alpha", null, SemverVersion.Parse("1.2.3-alpha")); + } + + [TestMethod, Priority(0)] + public void TestNumericPreRelease() { + SemverVersionTestHelper.AssertVersionsEqual(1, 2, 3, "4.5.6", null, SemverVersion.Parse("1.2.3-4.5.6")); + } + + [TestMethod, Priority(0)] + public void TestPreReleaseHyphenatedIdentifier() { + SemverVersionTestHelper.AssertVersionsEqual( + 1, + 2, + 3, + "alpha-2.1", + null, + SemverVersion.Parse("1.2.3-alpha-2.1")); + } + + [TestMethod, Priority(0)] + public void TestPreReleaseHyphenatedIdentifierWithoutVersion() + { + // This version code is crazy, and for that reason it fail when + // regional settings is set to Turkish. + SemverVersionTestHelper.AssertVersionsEqual( + 0, + 1, + 4, + "DEPRECATED-USE-cfenv-INSTEAD", + null, + SemverVersion.Parse("0.1.4-DEPRECATED-USE-cfenv-INSTEAD")); + } + + [TestMethod, Priority(0)] + public void TestPreReleaseAndBuildMetadata() { + // 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85 + SemverVersionTestHelper.AssertVersionsEqual(1, 0, 0, "alpha", "001", SemverVersion.Parse("1.0.0-alpha+001")); + SemverVersionTestHelper.AssertVersionsEqual( + 1, + 0, + 0, + "beta", + "exp.sha.5114f85", + SemverVersion.Parse("1.0.0-beta+exp.sha.5114f85")); + } + + [TestMethod, Priority(0)] + public void TestBuildMetadataOnly() { + SemverVersionTestHelper.AssertVersionsEqual( + 1, + 0, + 0, + null, + "20130313144700", + SemverVersion.Parse("1.0.0+20130313144700")); + } + } } \ No newline at end of file diff --git a/Nodejs/Tests/NpmTests/TemporaryFileManager.cs b/Nodejs/Tests/NpmTests/TemporaryFileManager.cs index 1698f2c32..3fa29c6a9 100644 --- a/Nodejs/Tests/NpmTests/TemporaryFileManager.cs +++ b/Nodejs/Tests/NpmTests/TemporaryFileManager.cs @@ -1,510 +1,510 @@ -//*********************************************************// -// Copyright (c) Microsoft. All rights reserved. -// -// Apache 2.0 License -// -// You may obtain a copy of the License at -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or -// implied. See the License for the specific language governing -// permissions and limitations under the License. -// -//*********************************************************// - -using System; -using System.Collections.Generic; -using System.IO; -using System.Text; - -namespace NpmTests { - /// - /// A class to manange a set of temporary files. The temporary files are deleted when Dispose or the finalizer is called. - /// - /// - /// Each TemporaryFileManager can have a pre- and postfix applied to each file/directory. - /// - public class TemporaryFileManager { - #region Static variables - - public const string DEFAULT_EXTENSION = "tmp"; - private static string _sTempPath; - private static readonly object _sStaticLock = new object(); - - #endregion - - #region Static Properties - - /// - /// Returns the default temporary directory used for all temporary files. - /// - public string DefaultTempPath { - get { - lock (_sStaticLock) { - if (_sTempPath == null) { - InitializeTempPath(); - } - return _sTempPath; - } - } - } - - #endregion - - #region Static methods - - /// - /// Generates a new file or directory name with the default extension. - /// This will be unmanaged by any instance. - /// - /// A new temporary file or directory name. - public string GenerateName() { - return GenerateName("", DEFAULT_EXTENSION); - } - - /// - /// Generates a new file or directory name with the specified extension. - /// This will be unmanaged by any instance. - /// - /// The extension to apply to the name, without the dot. Can be null or empty for no extension. - /// A new temporary file or directory name. - public string GenerateName(string extension) { - return GenerateName("", extension); - } - - /// - /// Generates a new file or directory name with the specified prefix and extension. - /// This will be unmanaged by any instance. - /// - /// The prefix to apply to the name. - /// The extension to apply to the name, without the dot. Can be null or empty for no extension. - /// A new temporary file or directory name. - public string GenerateName(string prefix, string extension) { - StringBuilder name = new StringBuilder(String.Format("{0}{1}", prefix, Guid.NewGuid())); - // add the extension if specified - if (!String.IsNullOrEmpty(extension)) { - name.Append('.').Append(extension); - } - return name.ToString(); - } - - private static void InitializeTempPath() { - // first try to use RGTEMP or a subdirectory of TempPath - string temppath = Environment.GetEnvironmentVariable("RGTEMP"); - if (String.IsNullOrEmpty(temppath)) { - temppath = Path.Combine(Path.GetTempPath(), @"Red Gate\"); - } - - string filename = null; - FileStream fs = null; - try { - // create it if it doesnt exist - if (!Directory.Exists(temppath)) { - Directory.CreateDirectory(temppath); - } - - // and check we can actually create a file - filename = Path.Combine(temppath, Guid.NewGuid().ToString()); - fs = File.Create(filename); - } catch { - temppath = Path.GetTempPath(); - } finally { - if (fs != null) { - fs.Close(); - File.Delete(filename); - } - } - _sTempPath = temppath; - } - - #endregion - - #region Member variables - - // the full paths to all the known files & directories managed by this instance - // the boolean indicates if it should be deleted on disposal - private readonly IDictionary _files = new Dictionary(); - private readonly IDictionary _directories = new Dictionary(); - private readonly string _prefix; - private readonly string _extension; - private readonly object _lock = new object(); - - #endregion - - #region Initialization - - /// - /// Initializes a new instance of the class with an empty prefix. - /// - public TemporaryFileManager() - : this("", DEFAULT_EXTENSION) { } - - /// - /// Initializes a new instance of the class with the specified prefix. - /// - /// The prefix to apply to all temporary file and directory names. - public TemporaryFileManager(string prefix) - : this(prefix, DEFAULT_EXTENSION) { } - - /// - /// Initializes a new instance of the class with the specified prefix - /// and default extension. - /// - /// The prefix to apply to all temporary file and directory names. - /// The default extension for all new temporary files (can be overridden per-file). - public TemporaryFileManager(string prefix, string defaultExtension) { - _prefix = prefix; - _extension = defaultExtension; - } - - ~TemporaryFileManager() { - Dispose(false); - } - - #endregion - - #region IDisposable implementation and ancillary methods - - private bool _disposed; - private string _objectName; - - public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Returns a value indicating if this instance has been disposed. - /// - public bool IsDisposed { - get { return _disposed; } - } - - /// - /// Gets and sets the object name to use for any thrown s. The default - /// is the Name property of . - /// - protected string ObjectName { - get { return !String.IsNullOrEmpty(_objectName) ? _objectName : GetType().Name; } - set { _objectName = value; } - } - - /// - /// Throws if this instance has been disposed. - /// - /// This instance has been disposed. - protected void CheckDisposed() { - if (_disposed) { - throw new ObjectDisposedException(ObjectName); - } - } - - #endregion - - #region Generating directory & filenames - - /// - /// Generates a unique filename within the specified subdirectory of . - /// - /// The subdirectory under which to create the file, relative to the . - /// The extension to apply to the filename, without the dot. - /// The full path to the temporary file. - public string GenerateUniqueFilePath(string parentDir, string extension) { - CheckDisposed(); - - string filePath; - lock (_lock) { - do { - do { - filePath = Path.Combine( - Path.Combine(DefaultTempPath, parentDir), - GenerateName(_prefix, extension)); - } while (_files.ContainsKey(filePath)); - } while (File.Exists(filePath) || Directory.Exists(filePath)); - } - return filePath; - } - - /// - /// Generates a unique directory name within the specified subdirectory of - /// - /// The full path to the directory. - public string GenerateUniqueDirectoryPath() { - CheckDisposed(); - - string fullPath; - lock (_lock) { - do { - do { - fullPath = Path.Combine(DefaultTempPath, GenerateName(_prefix, "")); - } while (_directories.ContainsKey(fullPath)); - } while (File.Exists(fullPath) || Directory.Exists(fullPath)); - } - - return fullPath; - } - - #endregion - - #region Creating files - - //[Obsolete("Please use GetNewTempFile instead")] - //public string GetTempFilename() { - // return GetNewTempFile(); - //} - - //[Obsolete("Please use GetNewTempFile instead")] - //public string GetTempFilename(bool createFile) { - // return GetNewTempFile(createFile, true); - //} - - /// - /// Creates a new temporary file in that will be deleted on disposal of the current instance. - /// - /// The full path of the temporary file. - /// You do not have permissions to create a file on . - /// An I/O error occurred while creating the file. - public string GetNewTempFile() { - return GetNewTempFile("", _extension, true, true); - } - - /// - /// Creates a new temporary file with the specified extension in - /// that will be deleted on disposal of the current instance. - /// - /// The extension to apply to the file. - /// The full path of the temporary file. - /// You do not have permissions to create a file on . - /// An I/O error occurred while creating the file. - public string GetNewTempFile(string extension) { - return GetNewTempFile("", extension, true, true); - } - - /// - /// Creates a new temporary file with the specified extension in the specified subdirectory of - /// that will be deleted on disposal of the current instance. - /// - /// The subdirectory of to create the file in. - /// The extension to apply to the file. - /// The full path of the temporary file. - /// You do not have permissions to create a file on . - /// The specified subdirectory does not exist. - /// An I/O error occurred while creating the file. - public string GetNewTempFile(string subDir, string extension) { - return GetNewTempFile(subDir, extension, true, true); - } - - /// - /// Returns the path to a new temporary file in . - /// - /// If true, the file is also created. - /// If true, the file will be deleted on disposal of this instance if it exists. - /// The full path of the temporary file. - /// You do not have permissions to create a file on . - /// An I/O error occurred while creating the file. - public string GetNewTempFile(bool createFile, bool deleteOnDispose) { - return GetNewTempFile("", _extension, createFile, deleteOnDispose); - } - - /// - /// Returns the path to a new temporary file with the specified extension - /// in the specified subdirectory of . - /// - /// The subdirectory to create the temporary file under. - /// The extension to apply to the file. - /// If true, the file is also created. - /// If true, the file will be deleted on disposal of this instance if it exists. - /// The path of the temporary file. - /// You do not have permissions to create a file on . - /// The specified subdirectory does not exist. - /// An I/O error occurred while creating the file. - public virtual string GetNewTempFile(string subDir, string extension, bool createFile, bool deleteOnDispose) { - CheckDisposed(); - - lock (_lock) { - string filename = GenerateUniqueFilePath(subDir, extension); - if (createFile) { - FileStream stream = null; - try { - using (stream = File.Create(filename)) { - _files.Add(filename, deleteOnDispose); - } - } finally { - if (stream != null) { - stream.Close(); - } - } - } else { - _files.Add(filename, deleteOnDispose); - } - - return filename; - } - } - - #endregion - - #region Creating directories - - //[Obsolete("Please use GetNewTempDirectory instead")] - //public string GetTempSubdirectoryName() { - // return GetNewTempDirectory().FullName; - //} - - //[Obsolete("Please use GetNewTempDirectory instead", true)] - //public string GetTempSubdirectoryName(bool createDirectory) { - // // we don't optionally create directories anymore - // throw new NotSupportedException(); - //} - - /// - /// Creates a new temporary directory in that will be deleted on disposal of the current instance. - /// - /// A object representing the new directory. - /// You do not have the necessary permissions to create a temporary directory. - public DirectoryInfo GetNewTempDirectory() { - return GetNewTempDirectory(true); - } - - /// - /// Creates a new temporary directory in . - /// - /// - /// If true, the directory and its contents will be recursively deleted on disposal of this instance, if it exists. - /// - /// A object representing the new directory. - /// You do not have the necessary permissions to create a temporary directory. - public virtual DirectoryInfo GetNewTempDirectory(bool deleteOnDispose) { - CheckDisposed(); - - lock (_lock) { - string dirpath = GenerateUniqueDirectoryPath(); - DirectoryInfo dirinfo = Directory.CreateDirectory(dirpath); - - _directories.Add(dirpath, deleteOnDispose); - return dirinfo; - } - } - - #endregion - - #region Registering extra temporary files - - /// - /// Add a file or directory to be managed by this instance. It will be deleted on disposal - /// of this instance. - /// - /// The absolute path to be managed by this instance - /// does not exist as a file or directory - public void RegisterFileOrDirectory(string path) { - lock (_lock) { - if (Directory.Exists(path)) { - _directories[path] = true; - } else if (File.Exists(path)) { - _files[path] = true; - } - } - } - - public void RegisterDirectory(string path) { - lock (_lock) { - _directories[path] = true; - } - } - - public void RegisterFile(string path) { - lock (_lock) { - _files[path] = true; - } - } - - #endregion - - #region Deleting temporary files & directories - - public bool IsFileManaged(string filename) { - return _files.ContainsKey(filename); - } - - /// - /// Deletes the specified temporary file. - /// - /// The path of the temporary file to delete. - /// This must have been returned by a previous call to GetNewTempFile. - /// True if the file was successfully deleted. False if the file is currently in use. - /// is not registered as a temporary file. - public bool DeleteFile(string filename) { - lock (_lock) { - if (!_files.ContainsKey(filename)) { - return false; - } - - File.SetAttributes(filename, FileAttributes.Normal); - try { - File.Delete(filename); - _files.Remove(filename); - return true; - } catch (IOException) { - return false; - } - } - } - - /// - /// Deletes the specified temporary directory and all its contents. - /// - /// The path of the temporary directory to delete. - /// This must have been returned by a previous call to GetNewTempDirectory. - /// True if the directory was successfully deleted. False if the directory is currently in use. - /// You do not have write permission to the directory. - public bool DeleteDirectory(string dirname) { - lock (_lock) { - if (!_directories.ContainsKey(dirname)) { - return false; - } - - try { - Directory.Delete(dirname, true); - _directories.Remove(dirname); - return true; - } catch (IOException) { - return false; - } - } - } - - #endregion - - #region IDisposable - - protected void Dispose(bool disposing) { - lock (_lock) { - if (!IsDisposed) { - _disposed = true; - // delete files we've been told to delete - // these are unmanaged resources, so delete the files wherever we're called from - foreach (KeyValuePair kvp in _files) { - try { - if (kvp.Value && File.Exists(kvp.Key)) { - File.Delete(kvp.Key); - } - } catch { } // don't care anymore - } - - //delete directories we've been told to delete - foreach (KeyValuePair kvp in _directories) { - try { - if (kvp.Value && Directory.Exists(kvp.Key)) { - Directory.Delete(kvp.Key, true); - } - } catch { } // don't care anymore - } - } - } - } - - #endregion - } +//*********************************************************// +// Copyright (c) Microsoft. All rights reserved. +// +// Apache 2.0 License +// +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. +// +//*********************************************************// + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace NpmTests { + /// + /// A class to manange a set of temporary files. The temporary files are deleted when Dispose or the finalizer is called. + /// + /// + /// Each TemporaryFileManager can have a pre- and postfix applied to each file/directory. + /// + public class TemporaryFileManager { + #region Static variables + + public const string DEFAULT_EXTENSION = "tmp"; + private static string _sTempPath; + private static readonly object _sStaticLock = new object(); + + #endregion + + #region Static Properties + + /// + /// Returns the default temporary directory used for all temporary files. + /// + public string DefaultTempPath { + get { + lock (_sStaticLock) { + if (_sTempPath == null) { + InitializeTempPath(); + } + return _sTempPath; + } + } + } + + #endregion + + #region Static methods + + /// + /// Generates a new file or directory name with the default extension. + /// This will be unmanaged by any instance. + /// + /// A new temporary file or directory name. + public string GenerateName() { + return GenerateName("", DEFAULT_EXTENSION); + } + + /// + /// Generates a new file or directory name with the specified extension. + /// This will be unmanaged by any instance. + /// + /// The extension to apply to the name, without the dot. Can be null or empty for no extension. + /// A new temporary file or directory name. + public string GenerateName(string extension) { + return GenerateName("", extension); + } + + /// + /// Generates a new file or directory name with the specified prefix and extension. + /// This will be unmanaged by any instance. + /// + /// The prefix to apply to the name. + /// The extension to apply to the name, without the dot. Can be null or empty for no extension. + /// A new temporary file or directory name. + public string GenerateName(string prefix, string extension) { + StringBuilder name = new StringBuilder(String.Format("{0}{1}", prefix, Guid.NewGuid())); + // add the extension if specified + if (!String.IsNullOrEmpty(extension)) { + name.Append('.').Append(extension); + } + return name.ToString(); + } + + private static void InitializeTempPath() { + // first try to use RGTEMP or a subdirectory of TempPath + string temppath = Environment.GetEnvironmentVariable("RGTEMP"); + if (String.IsNullOrEmpty(temppath)) { + temppath = Path.Combine(Path.GetTempPath(), @"Red Gate\"); + } + + string filename = null; + FileStream fs = null; + try { + // create it if it doesnt exist + if (!Directory.Exists(temppath)) { + Directory.CreateDirectory(temppath); + } + + // and check we can actually create a file + filename = Path.Combine(temppath, Guid.NewGuid().ToString()); + fs = File.Create(filename); + } catch { + temppath = Path.GetTempPath(); + } finally { + if (fs != null) { + fs.Close(); + File.Delete(filename); + } + } + _sTempPath = temppath; + } + + #endregion + + #region Member variables + + // the full paths to all the known files & directories managed by this instance + // the boolean indicates if it should be deleted on disposal + private readonly IDictionary _files = new Dictionary(); + private readonly IDictionary _directories = new Dictionary(); + private readonly string _prefix; + private readonly string _extension; + private readonly object _lock = new object(); + + #endregion + + #region Initialization + + /// + /// Initializes a new instance of the class with an empty prefix. + /// + public TemporaryFileManager() + : this("", DEFAULT_EXTENSION) { } + + /// + /// Initializes a new instance of the class with the specified prefix. + /// + /// The prefix to apply to all temporary file and directory names. + public TemporaryFileManager(string prefix) + : this(prefix, DEFAULT_EXTENSION) { } + + /// + /// Initializes a new instance of the class with the specified prefix + /// and default extension. + /// + /// The prefix to apply to all temporary file and directory names. + /// The default extension for all new temporary files (can be overridden per-file). + public TemporaryFileManager(string prefix, string defaultExtension) { + _prefix = prefix; + _extension = defaultExtension; + } + + ~TemporaryFileManager() { + Dispose(false); + } + + #endregion + + #region IDisposable implementation and ancillary methods + + private bool _disposed; + private string _objectName; + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Returns a value indicating if this instance has been disposed. + /// + public bool IsDisposed { + get { return _disposed; } + } + + /// + /// Gets and sets the object name to use for any thrown s. The default + /// is the Name property of . + /// + protected string ObjectName { + get { return !String.IsNullOrEmpty(_objectName) ? _objectName : GetType().Name; } + set { _objectName = value; } + } + + /// + /// Throws if this instance has been disposed. + /// + /// This instance has been disposed. + protected void CheckDisposed() { + if (_disposed) { + throw new ObjectDisposedException(ObjectName); + } + } + + #endregion + + #region Generating directory & filenames + + /// + /// Generates a unique filename within the specified subdirectory of . + /// + /// The subdirectory under which to create the file, relative to the . + /// The extension to apply to the filename, without the dot. + /// The full path to the temporary file. + public string GenerateUniqueFilePath(string parentDir, string extension) { + CheckDisposed(); + + string filePath; + lock (_lock) { + do { + do { + filePath = Path.Combine( + Path.Combine(DefaultTempPath, parentDir), + GenerateName(_prefix, extension)); + } while (_files.ContainsKey(filePath)); + } while (File.Exists(filePath) || Directory.Exists(filePath)); + } + return filePath; + } + + /// + /// Generates a unique directory name within the specified subdirectory of + /// + /// The full path to the directory. + public string GenerateUniqueDirectoryPath() { + CheckDisposed(); + + string fullPath; + lock (_lock) { + do { + do { + fullPath = Path.Combine(DefaultTempPath, GenerateName(_prefix, "")); + } while (_directories.ContainsKey(fullPath)); + } while (File.Exists(fullPath) || Directory.Exists(fullPath)); + } + + return fullPath; + } + + #endregion + + #region Creating files + + //[Obsolete("Please use GetNewTempFile instead")] + //public string GetTempFilename() { + // return GetNewTempFile(); + //} + + //[Obsolete("Please use GetNewTempFile instead")] + //public string GetTempFilename(bool createFile) { + // return GetNewTempFile(createFile, true); + //} + + /// + /// Creates a new temporary file in that will be deleted on disposal of the current instance. + /// + /// The full path of the temporary file. + /// You do not have permissions to create a file on . + /// An I/O error occurred while creating the file. + public string GetNewTempFile() { + return GetNewTempFile("", _extension, true, true); + } + + /// + /// Creates a new temporary file with the specified extension in + /// that will be deleted on disposal of the current instance. + /// + /// The extension to apply to the file. + /// The full path of the temporary file. + /// You do not have permissions to create a file on . + /// An I/O error occurred while creating the file. + public string GetNewTempFile(string extension) { + return GetNewTempFile("", extension, true, true); + } + + /// + /// Creates a new temporary file with the specified extension in the specified subdirectory of + /// that will be deleted on disposal of the current instance. + /// + /// The subdirectory of to create the file in. + /// The extension to apply to the file. + /// The full path of the temporary file. + /// You do not have permissions to create a file on . + /// The specified subdirectory does not exist. + /// An I/O error occurred while creating the file. + public string GetNewTempFile(string subDir, string extension) { + return GetNewTempFile(subDir, extension, true, true); + } + + /// + /// Returns the path to a new temporary file in . + /// + /// If true, the file is also created. + /// If true, the file will be deleted on disposal of this instance if it exists. + /// The full path of the temporary file. + /// You do not have permissions to create a file on . + /// An I/O error occurred while creating the file. + public string GetNewTempFile(bool createFile, bool deleteOnDispose) { + return GetNewTempFile("", _extension, createFile, deleteOnDispose); + } + + /// + /// Returns the path to a new temporary file with the specified extension + /// in the specified subdirectory of . + /// + /// The subdirectory to create the temporary file under. + /// The extension to apply to the file. + /// If true, the file is also created. + /// If true, the file will be deleted on disposal of this instance if it exists. + /// The path of the temporary file. + /// You do not have permissions to create a file on . + /// The specified subdirectory does not exist. + /// An I/O error occurred while creating the file. + public virtual string GetNewTempFile(string subDir, string extension, bool createFile, bool deleteOnDispose) { + CheckDisposed(); + + lock (_lock) { + string filename = GenerateUniqueFilePath(subDir, extension); + if (createFile) { + FileStream stream = null; + try { + using (stream = File.Create(filename)) { + _files.Add(filename, deleteOnDispose); + } + } finally { + if (stream != null) { + stream.Close(); + } + } + } else { + _files.Add(filename, deleteOnDispose); + } + + return filename; + } + } + + #endregion + + #region Creating directories + + //[Obsolete("Please use GetNewTempDirectory instead")] + //public string GetTempSubdirectoryName() { + // return GetNewTempDirectory().FullName; + //} + + //[Obsolete("Please use GetNewTempDirectory instead", true)] + //public string GetTempSubdirectoryName(bool createDirectory) { + // // we don't optionally create directories anymore + // throw new NotSupportedException(); + //} + + /// + /// Creates a new temporary directory in that will be deleted on disposal of the current instance. + /// + /// A object representing the new directory. + /// You do not have the necessary permissions to create a temporary directory. + public DirectoryInfo GetNewTempDirectory() { + return GetNewTempDirectory(true); + } + + /// + /// Creates a new temporary directory in . + /// + /// + /// If true, the directory and its contents will be recursively deleted on disposal of this instance, if it exists. + /// + /// A object representing the new directory. + /// You do not have the necessary permissions to create a temporary directory. + public virtual DirectoryInfo GetNewTempDirectory(bool deleteOnDispose) { + CheckDisposed(); + + lock (_lock) { + string dirpath = GenerateUniqueDirectoryPath(); + DirectoryInfo dirinfo = Directory.CreateDirectory(dirpath); + + _directories.Add(dirpath, deleteOnDispose); + return dirinfo; + } + } + + #endregion + + #region Registering extra temporary files + + /// + /// Add a file or directory to be managed by this instance. It will be deleted on disposal + /// of this instance. + /// + /// The absolute path to be managed by this instance + /// does not exist as a file or directory + public void RegisterFileOrDirectory(string path) { + lock (_lock) { + if (Directory.Exists(path)) { + _directories[path] = true; + } else if (File.Exists(path)) { + _files[path] = true; + } + } + } + + public void RegisterDirectory(string path) { + lock (_lock) { + _directories[path] = true; + } + } + + public void RegisterFile(string path) { + lock (_lock) { + _files[path] = true; + } + } + + #endregion + + #region Deleting temporary files & directories + + public bool IsFileManaged(string filename) { + return _files.ContainsKey(filename); + } + + /// + /// Deletes the specified temporary file. + /// + /// The path of the temporary file to delete. + /// This must have been returned by a previous call to GetNewTempFile. + /// True if the file was successfully deleted. False if the file is currently in use. + /// is not registered as a temporary file. + public bool DeleteFile(string filename) { + lock (_lock) { + if (!_files.ContainsKey(filename)) { + return false; + } + + File.SetAttributes(filename, FileAttributes.Normal); + try { + File.Delete(filename); + _files.Remove(filename); + return true; + } catch (IOException) { + return false; + } + } + } + + /// + /// Deletes the specified temporary directory and all its contents. + /// + /// The path of the temporary directory to delete. + /// This must have been returned by a previous call to GetNewTempDirectory. + /// True if the directory was successfully deleted. False if the directory is currently in use. + /// You do not have write permission to the directory. + public bool DeleteDirectory(string dirname) { + lock (_lock) { + if (!_directories.ContainsKey(dirname)) { + return false; + } + + try { + Directory.Delete(dirname, true); + _directories.Remove(dirname); + return true; + } catch (IOException) { + return false; + } + } + } + + #endregion + + #region IDisposable + + protected void Dispose(bool disposing) { + lock (_lock) { + if (!IsDisposed) { + _disposed = true; + // delete files we've been told to delete + // these are unmanaged resources, so delete the files wherever we're called from + foreach (KeyValuePair kvp in _files) { + try { + if (kvp.Value && File.Exists(kvp.Key)) { + File.Delete(kvp.Key); + } + } catch { } // don't care anymore + } + + //delete directories we've been told to delete + foreach (KeyValuePair kvp in _directories) { + try { + if (kvp.Value && Directory.Exists(kvp.Key)) { + Directory.Delete(kvp.Key, true); + } + } catch { } // don't care anymore + } + } + } + } + + #endregion + } } \ No newline at end of file