/// <summary>
        ///     Try to retrieve the current state for the specified project document.
        /// </summary>
        /// <param name="documentUri">
        ///     The project document URI.
        /// </param>
        /// <param name="reload">
        ///     Reload the project if it is already loaded?
        /// </param>
        /// <returns>
        ///     The project document.
        /// </returns>
        public async Task <ProjectDocument> GetProjectDocument(Uri documentUri, bool reload = false)
        {
            string projectFilePath = VSCodeDocumentUri.GetFileSystemPath(documentUri);

            bool            isNewProject    = false;
            ProjectDocument projectDocument = _projectDocuments.GetOrAdd(documentUri, _ =>
            {
                isNewProject = true;

                if (MasterProject == null)
                {
                    return(MasterProject = new MasterProjectDocument(this, documentUri, Log));
                }

                return(MasterProject);
            });

            try
            {
                if (isNewProject || reload)
                {
                    using (await projectDocument.Lock.WriterLockAsync())
                    {
                        await projectDocument.Load();
                    }
                }
            }
            catch (Exception loadError)
            {
                Log.Error(loadError, "Unexpected error loading file {ProjectFilePath}.", projectFilePath);
            }

            return(projectDocument);
        }
        /// <summary>
        ///     Get hover content for an <see cref="MSBuildImport"/>.
        /// </summary>
        /// <param name="import">
        ///     The <see cref="MSBuildImport"/>.
        /// </param>
        /// <returns>
        ///     The content, or <c>null</c> if no content is provided.
        /// </returns>
        public MarkedStringContainer Import(MSBuildImport import)
        {
            if (import == null)
            {
                throw new ArgumentNullException(nameof(import));
            }

            List <MarkedString> content = new List <MarkedString>
            {
                $"Import: `{import.Name}`"
            };

            StringBuilder imports = new StringBuilder("Imports:");

            imports.AppendLine();
            foreach (string projectFile in import.ImportedProjectFiles)
            {
                imports.AppendLine($"* [{Path.GetFileName(projectFile)}]({VSCodeDocumentUri.FromFileSystemPath(projectFile)})");
            }

            content.Add(
                imports.ToString()
                );

            string helpLink = MSBuildSchemaHelp.HelpLinkForElement(import.Element.Name);

            if (!String.IsNullOrWhiteSpace(helpLink))
            {
                content.Add(
                    $"[Help]({helpLink})"
                    );
            }

            return(new MarkedStringContainer(content));
        }
Ejemplo n.º 3
0
        /// <summary>
        ///     Get hover content for an <see cref="MSBuildProperty"/>.
        /// </summary>
        /// <param name="property">
        ///     The <see cref="MSBuildProperty"/>.
        /// </param>
        /// <returns>
        ///     The content, or <c>null</c> if no content is provided.
        /// </returns>
        public MarkedStringContainer Property(MSBuildProperty property)
        {
            if (property == null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            List <MarkedString> content = new List <MarkedString>
            {
                $"Property: `{property.Name}`"
            };

            string propertyHelp = MSBuildSchemaHelp.ForProperty(property.Name);

            if (propertyHelp != null)
            {
                content.Add(propertyHelp);
            }

            if (property.IsOverridden)
            {
                Position overridingDeclarationPosition = property.DeclaringXml.Location.ToNative();

                StringBuilder overrideDescription = new StringBuilder();
                string        declarationFile     = property.DeclaringXml.Location.File;
                if (declarationFile != property.Property.Xml.Location.File)
                {
                    Uri declarationDocumentUri = VSCodeDocumentUri.FromFileSystemPath(declarationFile);
                    overrideDescription.AppendLine(
                        $"Value overridden at {overridingDeclarationPosition} in [{Path.GetFileName(declarationFile)}]({declarationDocumentUri})."
                        );
                }
                else
                {
                    overrideDescription.AppendLine($"Value overridden at {overridingDeclarationPosition} in this file.");
                }

                overrideDescription.AppendLine();
                overrideDescription.AppendLine();
                overrideDescription.AppendLine(
                    $"Unused value: `{property.DeclaringXml.Value}`"
                    );
                overrideDescription.AppendLine();
                overrideDescription.AppendLine(
                    $"Actual value: `{property.Value}`"
                    );

                content.Add(overrideDescription.ToString());
            }
            else
            {
                content.Add($"Value: `{property.Value}`");
            }

            return(new MarkedStringContainer(content));
        }
Ejemplo n.º 4
0
        /// <summary>
        ///     Determine the NuGet package sources configured for the current project and create clients for them.
        /// </summary>
        /// <param name="cancellationToken">
        ///     An optional <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     <c>true</c>, if the package sources were loaded; otherwise, <c>false</c>.
        /// </returns>
        public virtual async Task <bool> ConfigurePackageSources(CancellationToken cancellationToken = default(CancellationToken))
        {
            try
            {
                _configuredPackageSources.Clear();
                _autoCompleteResources.Clear();

                bool includeLocalSources = Workspace.Configuration.NuGet.IncludeLocalSources;

                _configuredPackageSources.AddRange(
                    NuGetHelper.GetWorkspacePackageSources(ProjectFile.Directory.FullName)
                    .Where(
                        packageSource => packageSource.IsHttp || (includeLocalSources && packageSource.TrySourceAsUri?.Scheme == Uri.UriSchemeFile)
                        )
                    );

                Log.Information("{PackageSourceCount} package sources configured for project {ProjectFile}.",
                                _configuredPackageSources.Count,
                                VSCodeDocumentUri.GetFileSystemPath(DocumentUri)
                                );
                foreach (PackageSource packageSource in _configuredPackageSources)
                {
                    if (packageSource.IsMachineWide)
                    {
                        Log.Information("  Globally-configured package source {PackageSourceName} (v{PackageSourceProtocolVersion}) => {PackageSourceUri}",
                                        packageSource.Name,
                                        packageSource.ProtocolVersion,
                                        packageSource.SourceUri
                                        );
                    }
                    else
                    {
                        Log.Information("  Locally-configured package source {PackageSourceName} (v{PackageSourceProtocolVersion}) => {PackageSourceUri}",
                                        packageSource.Name,
                                        packageSource.ProtocolVersion,
                                        packageSource.SourceUri
                                        );
                    }
                }

                _autoCompleteResources.AddRange(
                    await NuGetHelper.GetAutoCompleteResources(_configuredPackageSources, cancellationToken)
                    );

                return(true);
            }
            catch (Exception packageSourceLoadError)
            {
                Log.Error(packageSourceLoadError, "Error configuring NuGet package sources for MSBuild project '{ProjectFileName}'.", ProjectFile.FullName);

                return(false);
            }
        }
Ejemplo n.º 5
0
        /// <summary>
        ///     Get hover content for an <see cref="MSBuildSdkImport"/>.
        /// </summary>
        /// <param name="sdkImport">
        ///     The <see cref="MSBuildSdkImport"/>.
        /// </param>
        /// <returns>
        ///     The content, or <c>null</c> if no content is provided.
        /// </returns>
        public MarkedStringContainer SdkImport(MSBuildSdkImport sdkImport)
        {
            if (sdkImport == null)
            {
                throw new ArgumentNullException(nameof(sdkImport));
            }

            StringBuilder imports = new StringBuilder("Imports:");

            imports.AppendLine();
            foreach (string projectFile in sdkImport.ImportedProjectFiles)
            {
                imports.AppendLine($"* [{Path.GetFileName(projectFile)}]({VSCodeDocumentUri.FromFileSystemPath(projectFile)})");
            }

            return(new MarkedStringContainer(
                       $"SDK Import: {sdkImport.Name}",
                       imports.ToString()
                       ));
        }
Ejemplo n.º 6
0
        /// <summary>
        ///     Create a new <see cref="ProjectDocument"/>.
        /// </summary>
        /// <param name="workspace">
        ///     The document workspace.
        /// </param>
        /// <param name="documentUri">
        ///     The document URI.
        /// </param>
        /// <param name="logger">
        ///     The application logger.
        /// </param>
        protected ProjectDocument(Workspace workspace, Uri documentUri, ILogger logger)
        {
            if (workspace == null)
            {
                throw new ArgumentNullException(nameof(workspace));
            }

            if (documentUri == null)
            {
                throw new ArgumentNullException(nameof(documentUri));
            }

            Workspace   = workspace;
            DocumentUri = documentUri;
            ProjectFile = new FileInfo(
                VSCodeDocumentUri.GetFileSystemPath(documentUri)
                );

            if (ProjectFile.Extension.EndsWith("proj", StringComparison.OrdinalIgnoreCase))
            {
                Kind = ProjectDocumentKind.Project;
            }
            else if (ProjectFile.Extension.Equals(".props", StringComparison.OrdinalIgnoreCase))
            {
                Kind = ProjectDocumentKind.Properties;
            }
            else if (ProjectFile.Extension.Equals(".targets", StringComparison.OrdinalIgnoreCase))
            {
                Kind = ProjectDocumentKind.Targets;
            }
            else
            {
                Kind = ProjectDocumentKind.Other;
            }

            Log = logger.ForContext(GetType()).ForContext("ProjectDocument", ProjectFile.FullName);
        }
        /// <summary>
        ///     Get hover content for an <see cref="MSBuildProperty"/>.
        /// </summary>
        /// <param name="property">
        ///     The <see cref="MSBuildProperty"/>.
        /// </param>
        /// <returns>
        ///     The content, or <c>null</c> if no content is provided.
        /// </returns>
        public MarkedStringContainer Property(MSBuildProperty property)
        {
            if (property == null)
            {
                throw new ArgumentNullException(nameof(property));
            }

            List <MarkedString> content = new List <MarkedString>
            {
                $"Property: `{property.Name}`"
            };

            string propertyHelp = MSBuildSchemaHelp.ForProperty(property.Name);

            if (propertyHelp != null)
            {
                content.Add(propertyHelp);
            }

            if (property.IsOverridden)
            {
                // BUG: This is the location of the *overridden* property, not the *overriding* property.
                //      We'll need to build a lookup by recursively following ProjectProperty.Predecessor.
                Position overridingDeclarationPosition = property.DeclaringXml.Location.ToNative();

                StringBuilder overrideDescription = new StringBuilder();
                string        declarationFile     = property.DeclaringXml.Location.File;
                if (declarationFile != property.Property.Xml.Location.File)
                {
                    Uri declarationDocumentUri = VSCodeDocumentUri.FromFileSystemPath(declarationFile);
                    overrideDescription.AppendLine(
                        $"Value overridden at {overridingDeclarationPosition} in [{Path.GetFileName(declarationFile)}]({declarationDocumentUri})."
                        );
                }
                else
                {
                    overrideDescription.AppendLine($"Value overridden at {overridingDeclarationPosition} in this file.");
                }

                overrideDescription.AppendLine();
                overrideDescription.AppendLine();
                overrideDescription.AppendLine(
                    $"Unused value: `{property.DeclaringXml.Value}`"
                    );
                overrideDescription.AppendLine();
                overrideDescription.AppendLine(
                    $"Actual value: `{property.Value}`"
                    );

                content.Add(overrideDescription.ToString());
            }
            else
            {
                content.Add($"Value: `{property.Value}`");
            }

            string helpLink = MSBuildSchemaHelp.HelpLinkForProperty(property.Name);

            if (!String.IsNullOrWhiteSpace(helpLink))
            {
                content.Add(
                    $"[Help]({helpLink})"
                    );
            }

            return(new MarkedStringContainer(content));
        }
        /// <summary>
        ///     Try to retrieve the current state for the specified project document.
        /// </summary>
        /// <param name="documentUri">
        ///     The project document URI.
        /// </param>
        /// <param name="reload">
        ///     Reload the project if it is already loaded?
        /// </param>
        /// <returns>
        ///     The project document.
        /// </returns>
        public async Task <ProjectDocument> GetProjectDocument(Uri documentUri, bool reload = false)
        {
            string projectFilePath = VSCodeDocumentUri.GetFileSystemPath(documentUri);

            bool            isNewProject    = false;
            ProjectDocument projectDocument = _projectDocuments.GetOrAdd(documentUri, _ =>
            {
                isNewProject = true;

                if (MasterProject == null)
                {
                    return(MasterProject = new MasterProjectDocument(this, documentUri, Log));
                }

                SubProjectDocument subProject = new SubProjectDocument(this, documentUri, Log, MasterProject);
                MasterProject.AddSubProject(subProject);

                return(subProject);
            });

            if (!_msbuildVersionLogged)
            {
                if (MSBuildHelper.HaveMSBuild)
                {
                    Log.Information("Using MSBuild engine v{MSBuildVersion:l} from {MSBuildPath}.",
                                    MSBuildHelper.MSBuildVersion,
                                    MSBuildHelper.MSBuildPath
                                    );
                }
                else
                {
                    Log.Warning("Failed to find any version of MSBuild compatible with the current .NET SDK (respecting global.json).");
                }

                _msbuildVersionLogged = true;
            }

            try
            {
                if (isNewProject || reload)
                {
                    using (await projectDocument.Lock.WriterLockAsync())
                    {
                        await projectDocument.Load();
                    }
                }
            }
            catch (XmlException invalidXml)
            {
                Log.Error("Error parsing project file {ProjectFilePath}: {ErrorMessage:l}",
                          projectFilePath,
                          invalidXml.Message
                          );
            }
            catch (Exception loadError)
            {
                Log.Error(loadError, "Unexpected error loading file {ProjectFilePath}.", projectFilePath);
            }

            return(projectDocument);
        }
        /// <summary>
        ///     Determine the NuGet package sources configured for the current project and create clients for them.
        /// </summary>
        /// <param name="cancellationToken">
        ///     An optional <see cref="CancellationToken"/> that can be used to cancel the operation.
        /// </param>
        /// <returns>
        ///     <c>true</c>, if the package sources were loaded; otherwise, <c>false</c>.
        /// </returns>
        public virtual async Task <bool> ConfigurePackageSources(CancellationToken cancellationToken = default(CancellationToken))
        {
            try
            {
                _configuredPackageSources.Clear();
                _autoCompleteResources.Clear();

                bool             includeLocalSources   = Workspace.Configuration.NuGet.IncludeLocalSources;
                HashSet <string> ignoredPackageSources = Workspace.Configuration.NuGet.IgnorePackageSources;

                foreach (PackageSource packageSource in NuGetHelper.GetWorkspacePackageSources(ProjectFile.Directory.FullName))
                {
                    // Exclude package sources explicitly ignored by name.
                    string packageSourceName = packageSource.Name ?? "<unknown>";
                    if (ignoredPackageSources.Contains(packageSourceName))
                    {
                        Log.Verbose("Ignoring package source named {PackageSourceName} (the language server has been explicitly configured to ignore it).", packageSourceName);

                        continue;
                    }

                    // Exclude package sources explicitly ignored by URI.
                    Uri packageSourceUri = packageSource.TrySourceAsUri ?? new Uri("unknown:/", UriKind.Absolute);
                    if (ignoredPackageSources.Contains(packageSourceUri.AbsoluteUri))
                    {
                        Log.Verbose("Ignoring package source with URI {PackageSourceURI} (the language server has been explicitly configured to ignore it).", packageSourceUri.AbsoluteUri);

                        continue;
                    }

                    // Exclude unsupported package-source types.
                    if (!packageSource.IsHttp)
                    {
                        if (packageSourceUri.Scheme == Uri.UriSchemeFile)
                        {
                            if (!includeLocalSources)
                            {
                                Log.Verbose("Ignoring local package source {PackageSourceName} ({PackageSourcePath}) (the language server has not been configured to use local package sources).",
                                            packageSourceName,
                                            packageSourceUri.AbsolutePath
                                            );

                                continue;
                            }
                        }
                        else
                        {
                            Log.Verbose("Ignoring local package source {PackageSourceName} ({PackageSourceUri}) (the language server only supports local and HTTP-based package sources).",
                                        packageSourceName,
                                        packageSourceUri.AbsolutePath
                                        );

                            continue;
                        }
                    }

                    _configuredPackageSources.Add(packageSource);
                }

                Log.Information("{PackageSourceCount} package sources configured for project {ProjectFile}.",
                                _configuredPackageSources.Count,
                                VSCodeDocumentUri.GetFileSystemPath(DocumentUri)
                                );
                foreach (PackageSource packageSource in _configuredPackageSources)
                {
                    if (packageSource.IsMachineWide)
                    {
                        Log.Information("  Globally-configured package source {PackageSourceName} (v{PackageSourceProtocolVersion}) => {PackageSourceUri}",
                                        packageSource.Name,
                                        packageSource.ProtocolVersion,
                                        packageSource.SourceUri
                                        );
                    }
                    else
                    {
                        Log.Information("  Locally-configured package source {PackageSourceName} (v{PackageSourceProtocolVersion}) => {PackageSourceUri}",
                                        packageSource.Name,
                                        packageSource.ProtocolVersion,
                                        packageSource.SourceUri
                                        );
                    }
                }

                List <SourceRepository> sourceRepositories = _configuredPackageSources.CreateResourceRepositories();
                foreach (SourceRepository sourceRepository in sourceRepositories)
                {
                    ServiceIndexResourceV3 serviceIndex = await sourceRepository.GetResourceAsync <ServiceIndexResourceV3>(cancellationToken);

                    if (serviceIndex == null)
                    {
                        Log.Warning("    Ignoring configured package source {PackageSourceName} ({PackageSourceUri}) because the v3 service index cannot be found for this package source.",
                                    sourceRepository.PackageSource.Name ?? "<unknown>",
                                    sourceRepository.PackageSource.TrySourceAsUri?.AbsoluteUri ?? "unknown:/"
                                    );

                        continue;
                    }

                    IReadOnlyList <ServiceIndexEntry> autoCompleteServices = serviceIndex.GetServiceEntries(ServiceTypes.SearchAutocompleteService);
                    if (autoCompleteServices.Count == 0)
                    {
                        Log.Warning("    Ignoring configured package source {PackageSourceName} ({PackageSourceUri}) because it does not appear to support a compatible version of the NuGet auto-complete API.",
                                    sourceRepository.PackageSource.Name ?? "<unknown>",
                                    sourceRepository.PackageSource.TrySourceAsUri?.AbsoluteUri ?? "unknown:/"
                                    );

                        continue;
                    }

                    AutoCompleteResource autoCompleteResource = await sourceRepository.GetResourceAsync <AutoCompleteResource>(cancellationToken);

                    if (autoCompleteResource == null)
                    {
                        // Should not happen.
                        Log.Error("Failed to retrieve {ServiceName} service instance for configured package source {PackageSourceName} ({PackageSourceUri}).",
                                  "AutoComplete",
                                  sourceRepository.PackageSource.Name ?? "<unknown>",
                                  sourceRepository.PackageSource.TrySourceAsUri?.AbsoluteUri ?? "unknown:/"
                                  );

                        continue;
                    }

                    _autoCompleteResources.Add(autoCompleteResource);
                }

                return(true);
            }
            catch (Exception packageSourceLoadError)
            {
                Log.Error(packageSourceLoadError, "Error configuring NuGet package sources for MSBuild project '{ProjectFileName}'.", ProjectFile.FullName);

                return(false);
            }
        }