Example #1
0
        private IDependency?FindDependency(GraphNode inputGraphNode, out DependenciesSnapshot?snapshot)
        {
            string?projectPath = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.Assembly);

            if (string.IsNullOrWhiteSpace(projectPath))
            {
                snapshot = null;
                return(null);
            }

            string projectFolder = Path.GetDirectoryName(projectPath);

            if (projectFolder == null)
            {
                snapshot = null;
                return(null);
            }

            string?id = inputGraphNode.GetValue <string>(DependenciesGraphSchema.DependencyIdProperty);

            bool topLevel;

            if (id == null)
            {
                // this is top level node and it contains full path
                id = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.File);

                if (id == null)
                {
                    // No full path, so this must be a node generated by a different provider.
                    snapshot = null;
                    return(null);
                }

                if (id.StartsWith(projectFolder, StringComparison.OrdinalIgnoreCase))
                {
                    int startIndex = projectFolder.Length;

                    // Trim backslashes (without allocating)
                    while (startIndex < id.Length && id[startIndex] == '\\')
                    {
                        startIndex++;
                    }

                    id = id.Substring(startIndex);
                }

                topLevel = true;
            }
            else
            {
                topLevel = false;
            }

            snapshot = AggregateSnapshotProvider.GetSnapshot(projectPath !);

            return(snapshot?.FindDependency(id, topLevel));
        }
Example #2
0
        /// <summary>
        /// Gets a value indicating whether deleting a given set of items from the project, and optionally from disk,
        /// would be allowed.
        /// Note: CanRemove can be called several times since there two types of remove operations:
        ///   - Remove is a command that can remove project tree items form the tree/project but not from disk.
        ///     For that command requests deleteOptions has DeleteOptions.None flag.
        ///   - Delete is a command that can remove project tree items and form project and from disk.
        ///     For this command requests deleteOptions has DeleteOptions.DeleteFromStorage flag.
        /// We can potentially support only Remove command here, since we don't remove Dependencies form disk,
        /// thus we return false when DeleteOptions.DeleteFromStorage is provided.
        /// </summary>
        /// <param name="nodes">The nodes that should be deleted.</param>
        /// <param name="deleteOptions">
        /// A value indicating whether the items should be deleted from disk as well as from the project file.
        /// </param>
        public override bool CanRemove(IImmutableSet <IProjectTree> nodes,
                                       DeleteOptions deleteOptions = DeleteOptions.None)
        {
            if (deleteOptions.HasFlag(DeleteOptions.DeleteFromStorage))
            {
                return(false);
            }

            DependenciesSnapshot snapshot = _dependenciesSnapshotProvider.CurrentSnapshot;

            if (snapshot == null)
            {
                return(false);
            }

            foreach (IProjectTree node in nodes)
            {
                if (!node.Flags.Contains(DependencyTreeFlags.SupportsRemove))
                {
                    return(false);
                }

                string filePath = UnconfiguredProject.MakeRelative(node.FilePath);
                if (string.IsNullOrEmpty(filePath))
                {
                    continue;
                }

                IDependency?dependency = snapshot.FindDependency(filePath, topLevel: true);
                if (dependency == null || dependency.Implicit)
                {
                    return(false);
                }
            }

            return(true);
        }
        /// <summary>
        /// ProjectContextChanged gets fired every time dependencies change for projects across solution.
        /// <see cref="_expandedGraphContexts"/> contains all nodes that we need to check for potential updates
        /// in their child dependencies.
        /// </summary>
        private void OnSnapshotChanged(object sender, SnapshotChangedEventArgs e)
        {
            DependenciesSnapshot snapshot = e.Snapshot;

            if (snapshot == null || e.Token.IsCancellationRequested)
            {
                return;
            }

            // _expandedGraphContexts remembers graph expanded or checked so far.
            // Each context represents one level in the graph, i.e. a node and its first level dependencies
            // Tracking changes over all expanded contexts ensures that all levels are processed
            // and updated when there are any changes in nodes data.
            lock (_lock)
            {
                foreach (IGraphContext graphContext in _expandedGraphContexts)
                {
                    if (e.Token.IsCancellationRequested)
                    {
                        return;
                    }

                    bool anyChanges = false;

                    try
                    {
                        if (HandleChanges(graphContext))
                        {
                            anyChanges = true;
                        }
                    }
                    finally
                    {
                        // Calling OnCompleted ensures that the changes are reflected in UI
                        if (anyChanges)
                        {
                            graphContext.OnCompleted();
                        }
                    }
                }
            }

            return;

            bool HandleChanges(IGraphContext graphContext)
            {
                bool anyChanges = false;

                foreach (GraphNode inputGraphNode in graphContext.InputNodes.ToList())
                {
                    string?existingDependencyId = inputGraphNode.GetValue <string>(DependenciesGraphSchema.DependencyIdProperty);
                    if (string.IsNullOrEmpty(existingDependencyId))
                    {
                        continue;
                    }

                    string?nodeProjectPath = inputGraphNode.Id.GetValue(CodeGraphNodeIdName.Assembly);
                    if (Strings.IsNullOrEmpty(nodeProjectPath))
                    {
                        continue;
                    }

                    DependenciesSnapshot?updatedSnapshot = _aggregateSnapshotProvider.GetSnapshot(nodeProjectPath);

                    IDependency?updatedDependency = updatedSnapshot?.FindDependency(existingDependencyId);

                    if (updatedDependency == null) // or updatedSnapshot == null
                    {
                        continue;
                    }

                    IDependenciesGraphViewProvider?viewProvider = _viewProviders
                                                                  .FirstOrDefaultValue((x, d) => x.SupportsDependency(d), updatedDependency);
                    if (viewProvider == null)
                    {
                        continue;
                    }

                    if (!viewProvider.ShouldApplyChanges(nodeProjectPath, snapshot.ProjectPath, updatedDependency))
                    {
                        continue;
                    }

                    using var scope = new GraphTransactionScope();
                    if (viewProvider.ApplyChanges(
                            graphContext,
                            nodeProjectPath,
                            updatedDependency,
                            inputGraphNode,
                            updatedSnapshot !.DependenciesByTargetFramework[updatedDependency.TargetFramework]))
                    {
                        anyChanges = true;
                    }

                    scope.Complete();
                }

                return(anyChanges);
            }
        }
Example #4
0
        /// <summary>
        /// Deletes items from the project, and optionally from disk.
        /// Note: Delete and Remove commands are handled via IVsHierarchyDeleteHandler3, not by
        /// IAsyncCommandGroupHandler and first asks us we CanRemove nodes. If yes then RemoveAsync is called.
        /// We can remove only nodes that are standard and based on project items, i.e. nodes that
        /// are created by default IProjectDependenciesSubTreeProvider implementations and have
        /// DependencyNode.GenericDependencyFlags flags and IRule with Context != null, in order to obtain
        /// node's itemSpec. ItemSpec then used to remove a project item having same Include.
        /// </summary>
        /// <param name="nodes">The nodes that should be deleted.</param>
        /// <param name="deleteOptions">A value indicating whether the items should be deleted from disk as well as
        /// from the project file.
        /// </param>
        /// <exception cref="InvalidOperationException">Thrown when <see cref="IProjectTreeProvider.CanRemove"/>
        /// would return <c>false</c> for this operation.</exception>
        public override async Task RemoveAsync(IImmutableSet <IProjectTree> nodes,
                                               DeleteOptions deleteOptions = DeleteOptions.None)
        {
            if (deleteOptions.HasFlag(DeleteOptions.DeleteFromStorage))
            {
                throw new NotSupportedException();
            }

            // Get the list of shared import nodes.
            IEnumerable <IProjectTree> sharedImportNodes = nodes.Where(node =>
                                                                       node.Flags.Contains(DependencyTreeFlags.SharedProjectFlags));

            // Get the list of normal reference Item Nodes (this excludes any shared import nodes).
            IEnumerable <IProjectTree> referenceItemNodes = nodes.Except(sharedImportNodes);

            Assumes.NotNull(ActiveConfiguredProject);

            await _projectAccessor.OpenProjectForWriteAsync(ActiveConfiguredProject, project =>
            {
                // Handle the removal of normal reference Item Nodes (this excludes any shared import nodes).
                foreach (IProjectTree node in referenceItemNodes)
                {
                    if (node.BrowseObjectProperties?.Context == null)
                    {
                        // if node does not have an IRule with valid ProjectPropertiesContext we can not
                        // get its itemsSpec. If nodes provided by custom IProjectDependenciesSubTreeProvider
                        // implementation, and have some custom IRule without context, it is not a problem,
                        // since they would not have DependencyNode.GenericDependencyFlags and we would not
                        // end up here, since CanRemove would return false and Remove command would not show
                        // up for those nodes.
                        continue;
                    }

                    IProjectPropertiesContext nodeItemContext = node.BrowseObjectProperties.Context;
                    ProjectItem?unresolvedReferenceItem       = project.GetItemsByEvaluatedInclude(nodeItemContext.ItemName)
                                                                .FirstOrDefault(
                        (item, t) => string.Equals(item.ItemType, t, StringComparisons.ItemTypes),
                        nodeItemContext.ItemType);

                    Report.IfNot(unresolvedReferenceItem != null, "Cannot find reference to remove.");
                    if (unresolvedReferenceItem != null)
                    {
                        project.RemoveItem(unresolvedReferenceItem);
                    }
                }

                DependenciesSnapshot snapshot = _dependenciesSnapshotProvider.CurrentSnapshot;
                Requires.NotNull(snapshot, nameof(snapshot));
                if (snapshot == null)
                {
                    return;
                }

                // Handle the removal of shared import nodes.
                ProjectRootElement projectXml = project.Xml;
                foreach (IProjectTree sharedImportNode in sharedImportNodes)
                {
                    string sharedFilePath = UnconfiguredProject.MakeRelative(sharedImportNode.FilePath);
                    if (string.IsNullOrEmpty(sharedFilePath))
                    {
                        continue;
                    }

                    IDependency?sharedProjectDependency = snapshot.FindDependency(sharedFilePath, topLevel: true);
                    if (sharedProjectDependency != null)
                    {
                        sharedFilePath = sharedProjectDependency.Path;
                    }

                    // Find the import that is included in the evaluation of the specified ConfiguredProject that
                    // imports the project file whose full path matches the specified one.
                    IEnumerable <ResolvedImport> matchingImports = from import in project.Imports
                                                                   where import.ImportingElement.ContainingProject == projectXml &&
                                                                   PathHelper.IsSamePath(import.ImportedProject.FullPath, sharedFilePath)
                                                                   select import;
                    foreach (ResolvedImport importToRemove in matchingImports)
                    {
                        ProjectImportElement importingElementToRemove = importToRemove.ImportingElement;
                        Report.IfNot(importingElementToRemove != null,
                                     "Cannot find shared project reference to remove.");
                        if (importingElementToRemove != null)
                        {
                            importingElementToRemove.Parent.RemoveChild(importingElementToRemove);
                        }
                    }
                }
            });
        }