未验证 提交 612510d6 编写于 作者: G Gen Lu 提交者: GitHub

Merge pull request #30921 from genlu/projectinfo

Add support for default namespace to workspace
......@@ -252,6 +252,23 @@ public async Task Project_AssemblyName_Change()
}
}
[Fact]
public async Task Project_DefaultNamespace_Change()
{
using (var workspace = new WorkCoordinatorWorkspace(SolutionCrawler))
{
var solutionInfo = GetInitialSolutionInfo_2Projects_10Documents(workspace);
workspace.OnSolutionAdded(solutionInfo);
await WaitWaiterAsync(workspace.ExportProvider);
var project = workspace.CurrentSolution.Projects.First(p => p.Name == "P1").WithDefaultNamespace("newNamespace");
var worker = await ExecuteOperation(workspace, w => w.ChangeProject(project.Id, project.Solution));
Assert.Equal(5, worker.SyntaxDocumentIds.Count);
Assert.Equal(5, worker.DocumentIds.Count);
}
}
[Fact]
public async Task Project_AnalyzerOptions_Change()
{
......
......@@ -505,7 +505,8 @@ private async Task EnqueueProjectConfigurationChangeWorkItemAsync(ProjectChanges
!object.Equals(oldProject.CompilationOptions, newProject.CompilationOptions) ||
!object.Equals(oldProject.AssemblyName, newProject.AssemblyName) ||
!object.Equals(oldProject.Name, newProject.Name) ||
!object.Equals(oldProject.AnalyzerOptions, newProject.AnalyzerOptions))
!object.Equals(oldProject.AnalyzerOptions, newProject.AnalyzerOptions) ||
!object.Equals(oldProject.DefaultNamespace, newProject.DefaultNamespace))
{
projectConfigurationChange = projectConfigurationChange.With(InvocationReasons.ProjectConfigurationChanged);
}
......
......@@ -74,6 +74,7 @@ internal abstract partial class AbstractLegacyProject : ForegroundThreadAffiniti
FilePath = projectFilePath,
Hierarchy = hierarchy,
ProjectGuid = GetProjectIDGuid(hierarchy),
DefaultNamespace = GetDefaultNamespace(hierarchy, language)
});
Hierarchy = hierarchy;
......@@ -302,5 +303,50 @@ private static void ComputeFolderNames(uint folderItemID, List<string> names, IV
}
}
}
/// <summary>
/// Get the default namespace of the project ("" if not defined, which means global namespace),
/// or null if it is unknown or not applicable.
/// </summary>
/// <remarks>
/// This only has meaning in C# and is explicitly set to null in VB.
/// </remarks>>
private static string GetDefaultNamespace(IVsHierarchy hierarchy, string language)
{
// While both csproj and vbproj might define <rootnamespace> property in the project file,
// they are very different things.
//
// In C#, it's called default namespace (even though we got the value from rootnamespace property),
// and it doesn't affect the semantic of the code in anyway, just something used by VS.
// For example, when you create a new class, the namespace for the new class is based on it.
// Therefore, we can't get this info from compiler.
//
// However, in VB, it's actually called root namespace, and that info is part of the VB compilation
// (parsed from arguments), because VB compiler needs it to determine the root of all the namespace
// declared in the compilation.
//
// Unfortunately, although being different concepts, default namespace and root namespace are almost
// used interchangebly in VS. For example, (1) the value is define in "rootnamespace" property in project
// files and, (2) the property name we use to call into DTE project below to retrieve the value is
// called "DefaultNamespace".
if (hierarchy != null && language == LanguageNames.CSharp)
{
if (hierarchy.TryGetProject(out var dteProject))
{
try
{
return (string)dteProject.Properties.Item("DefaultNamespace").Value;
}
catch (ArgumentException)
{
// DefaultNamespace does not exist for this project.
return string.Empty;
}
}
}
return null;
}
}
}
......@@ -13,5 +13,7 @@ internal sealed class VisualStudioProjectCreationInfo
public IVsHierarchy Hierarchy { get; set; }
public Guid ProjectGuid { get; set; }
public string DefaultNamespace { get; set; }
}
}
......@@ -51,7 +51,8 @@ public VisualStudioProject CreateAndAddToWorkspace(string projectUniqueName, str
language: language,
filePath: creationInfo.FilePath,
compilationOptions: creationInfo.CompilationOptions,
parseOptions: creationInfo.ParseOptions);
parseOptions: creationInfo.ParseOptions)
.WithDefaultNamespace(creationInfo.DefaultNamespace);
// HACK: update this since we're still on the UI thread. Note we can only update this if we don't have projects -- the workspace
// only lets us really do this with OnSolutionAdded for now.
......
......@@ -177,6 +177,14 @@ public async Task TestUpdateOutputRefFilePath()
await VerifySolutionUpdate(code, s => s.WithProjectOutputRefFilePath(s.ProjectIds[0], "test.dll"));
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public async Task TestUpdateDefaultNamespace()
{
var code = @"class Test { void Method() { } }";
await VerifySolutionUpdate(code, s => s.WithProjectDefaultNamespace(s.ProjectIds[0], "TestClassLibrary"));
}
[Fact, Trait(Traits.Feature, Traits.Features.RemoteHost)]
public async Task TestUpdateDocumentInfo()
{
......
......@@ -384,7 +384,8 @@ private Task<ProjectInfo> CreateProjectInfoAsync(ProjectFileInfo projectFileInfo
analyzerReferences: analyzerReferences,
additionalDocuments: additionalDocuments,
isSubmission: false,
hostObjectType: null);
hostObjectType: null)
.WithDefaultNamespace(projectFileInfo.DefaultNamespace);
});
}
......
......@@ -119,6 +119,19 @@ private ProjectFileInfo CreateProjectFileInfo(MSB.Execution.ProjectInstance proj
outputRefFilePath = GetAbsolutePathRelativeToProject(outputRefFilePath);
}
// Get the default namespace for C# project, which is a C# only concept at the moment.
// We need the language check because "rootnamespace" property is also used in VB for
// completely different purpose.
string defaultNamespace = null;
if (Language == LanguageNames.CSharp)
{
defaultNamespace = project.ReadPropertyString(PropertyNames.RootNamespace);
if (string.IsNullOrWhiteSpace(defaultNamespace))
{
defaultNamespace = string.Empty;
}
}
var targetFramework = project.ReadPropertyString(PropertyNames.TargetFramework);
if (string.IsNullOrWhiteSpace(targetFramework))
{
......@@ -139,6 +152,7 @@ private ProjectFileInfo CreateProjectFileInfo(MSB.Execution.ProjectInstance proj
project.FullPath,
outputFilePath,
outputRefFilePath,
defaultNamespace,
targetFramework,
commandLineArgs,
docs,
......
......@@ -35,6 +35,15 @@ internal sealed class ProjectFileInfo
/// </summary>
public string OutputRefFilePath { get; }
/// <summary>
/// The default namespace of the project ("" if not defined, which means global namespace),
/// or null if it is unknown or not applicable.
/// </summary>
/// <remarks>
/// This only has meaning in C# and is explicitly set to null in VB.
/// </remarks>>
public string DefaultNamespace { get; }
/// <summary>
/// The target framework of this project.
/// This takes the form of the 'short name' form used by NuGet (e.g. net46, netcoreapp2.0, etc.)
......@@ -78,6 +87,7 @@ public override string ToString()
string filePath,
string outputFilePath,
string outputRefFilePath,
string defaultNamespace,
string targetFramework,
ImmutableArray<string> commandLineArgs,
ImmutableArray<DocumentFileInfo> documents,
......@@ -92,6 +102,7 @@ public override string ToString()
this.FilePath = filePath;
this.OutputFilePath = outputFilePath;
this.OutputRefFilePath = outputRefFilePath;
this.DefaultNamespace = defaultNamespace;
this.TargetFramework = targetFramework;
this.CommandLineArgs = commandLineArgs;
this.Documents = documents;
......@@ -105,6 +116,7 @@ public override string ToString()
string filePath,
string outputFilePath,
string outputRefFilePath,
string defaultNamespace,
string targetFramework,
ImmutableArray<string> commandLineArgs,
ImmutableArray<DocumentFileInfo> documents,
......@@ -117,6 +129,7 @@ public override string ToString()
filePath,
outputFilePath,
outputRefFilePath,
defaultNamespace,
targetFramework,
commandLineArgs,
documents,
......@@ -131,6 +144,7 @@ public static ProjectFileInfo CreateEmpty(string language, string filePath, Diag
filePath,
outputFilePath: null,
outputRefFilePath: null,
defaultNamespace: null,
targetFramework: null,
commandLineArgs: ImmutableArray<string>.Empty,
documents: ImmutableArray<DocumentFileInfo>.Empty,
......
......@@ -62,6 +62,15 @@ internal Project(Solution solution, ProjectState projectState)
/// </summary>
public string OutputRefFilePath => _projectState.OutputRefFilePath;
/// <summary>
/// The default namespace of the project ("" if not defined, which means global namespace),
/// or null if it is unknown or not applicable.
/// </summary>
/// <remarks>
/// This only has meaning in C# and is explicitly set to null in VB.
/// </remarks>>
internal string DefaultNamespace => _projectState.DefaultNamespace;
/// <summary>
/// <see langword="true"/> if this <see cref="Project"/> supports providing data through the
/// <see cref="GetCompilationAsync(CancellationToken)"/> method.
......@@ -342,6 +351,14 @@ public Project WithAssemblyName(string assemblyName)
return this.Solution.WithProjectAssemblyName(this.Id, assemblyName).GetProject(this.Id);
}
/// <summary>
/// Creates a new instance of this project updated to have the new default namespace.
/// </summary>
internal Project WithDefaultNamespace(string defaultNamespace)
{
return this.Solution.WithProjectDefaultNamespace(this.Id, defaultNamespace).GetProject(this.Id);
}
/// <summary>
/// Creates a new instance of this project updated to have the specified compilation options.
/// </summary>
......
......@@ -58,6 +58,15 @@ public sealed class ProjectInfo
/// </summary>
public string OutputRefFilePath => Attributes.OutputRefFilePath;
/// <summary>
/// The default namespace of the project ("" if not defined, which means global namespace),
/// or null if it is unknown or not applicable.
/// </summary>
/// <remarks>
/// This only has meaning in C# and is explicitly set to null in VB.
/// </remarks>>
internal string DefaultNamespace => Attributes.DefaultNamespace;
/// <summary>
/// True if this is a submission project for interactive sessions.
/// </summary>
......@@ -144,6 +153,7 @@ public sealed class ProjectInfo
string filePath,
string outputFilePath,
string outputRefFilePath,
string defaultNamespace,
CompilationOptions compilationOptions,
ParseOptions parseOptions,
IEnumerable<DocumentInfo> documents,
......@@ -165,6 +175,7 @@ public sealed class ProjectInfo
filePath,
outputFilePath,
outputRefFilePath,
defaultNamespace,
isSubmission,
hasAllInformation),
compilationOptions,
......@@ -201,7 +212,7 @@ public sealed class ProjectInfo
{
return Create(
id, version, name, assemblyName, language,
filePath, outputFilePath, outputRefFilePath: null, compilationOptions, parseOptions,
filePath, outputFilePath, outputRefFilePath: null, defaultNamespace: null, compilationOptions, parseOptions,
documents, projectReferences, metadataReferences, analyzerReferences, additionalDocuments,
isSubmission, hostObjectType, hasAllInformation: true);
}
......@@ -230,7 +241,7 @@ public sealed class ProjectInfo
{
return Create(
id, version, name, assemblyName, language,
filePath, outputFilePath, outputRefFilePath, compilationOptions, parseOptions,
filePath, outputFilePath, outputRefFilePath, defaultNamespace: null, compilationOptions, parseOptions,
documents, projectReferences, metadataReferences, analyzerReferences, additionalDocuments,
isSubmission, hostObjectType, hasAllInformation: true);
}
......@@ -321,6 +332,11 @@ public ProjectInfo WithOutputRefFilePath(string outputRefFilePath)
return With(attributes: Attributes.With(outputRefPath: outputRefFilePath));
}
internal ProjectInfo WithDefaultNamespace(string defaultNamespace)
{
return With(attributes: Attributes.With(defaultNamespace: defaultNamespace));
}
public ProjectInfo WithCompilationOptions(CompilationOptions compilationOptions)
{
return With(compilationOptions: compilationOptions);
......@@ -402,6 +418,11 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable
/// </summary>
public string OutputRefFilePath { get; }
/// <summary>
/// The default namespace of the project.
/// </summary>
public string DefaultNamespace { get; }
/// <summary>
/// True if this is a submission project for interactive sessions.
/// </summary>
......@@ -423,6 +444,7 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable
string filePath,
string outputFilePath,
string outputRefFilePath,
string defaultNamespace,
bool isSubmission,
bool hasAllInformation)
{
......@@ -435,6 +457,7 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable
FilePath = filePath;
OutputFilePath = outputFilePath;
OutputRefFilePath = outputRefFilePath;
DefaultNamespace = defaultNamespace;
IsSubmission = isSubmission;
HasAllInformation = hasAllInformation;
}
......@@ -447,6 +470,7 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable
Optional<string> filePath = default,
Optional<string> outputPath = default,
Optional<string> outputRefPath = default,
Optional<string> defaultNamespace = default,
Optional<bool> isSubmission = default,
Optional<bool> hasAllInformation = default)
{
......@@ -457,6 +481,7 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable
var newFilepath = filePath.HasValue ? filePath.Value : FilePath;
var newOutputPath = outputPath.HasValue ? outputPath.Value : OutputFilePath;
var newOutputRefPath = outputRefPath.HasValue ? outputRefPath.Value : OutputRefFilePath;
var newDefaultNamespace = defaultNamespace.HasValue ? defaultNamespace.Value : DefaultNamespace;
var newIsSubmission = isSubmission.HasValue ? isSubmission.Value : IsSubmission;
var newHasAllInformation = hasAllInformation.HasValue ? hasAllInformation.Value : HasAllInformation;
......@@ -467,6 +492,7 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable
newFilepath == FilePath &&
newOutputPath == OutputFilePath &&
newOutputRefPath == OutputRefFilePath &&
newDefaultNamespace == DefaultNamespace &&
newIsSubmission == IsSubmission &&
newHasAllInformation == HasAllInformation)
{
......@@ -482,6 +508,7 @@ internal class ProjectAttributes : IChecksummedObject, IObjectWritable
newFilepath,
newOutputPath,
newOutputRefPath,
newDefaultNamespace,
newIsSubmission,
newHasAllInformation);
}
......@@ -501,6 +528,7 @@ public void WriteTo(ObjectWriter writer)
writer.WriteString(FilePath);
writer.WriteString(OutputFilePath);
writer.WriteString(OutputRefFilePath);
writer.WriteString(DefaultNamespace);
writer.WriteBoolean(IsSubmission);
writer.WriteBoolean(HasAllInformation);
......@@ -519,10 +547,11 @@ public static ProjectAttributes ReadFrom(ObjectReader reader)
var filePath = reader.ReadString();
var outputFilePath = reader.ReadString();
var outputRefFilePath = reader.ReadString();
var defaultNamespace = reader.ReadString();
var isSubmission = reader.ReadBoolean();
var hasAllInformation = reader.ReadBoolean();
return new ProjectAttributes(projectId, VersionStamp.Create(), name, assemblyName, language, filePath, outputFilePath, outputRefFilePath, isSubmission, hasAllInformation);
return new ProjectAttributes(projectId, VersionStamp.Create(), name, assemblyName, language, filePath, outputFilePath, outputRefFilePath, defaultNamespace, isSubmission, hasAllInformation);
}
private Checksum _lazyChecksum;
......
......@@ -268,6 +268,9 @@ public async Task<VersionStamp> GetSemanticVersionAsync(CancellationToken cancel
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public string OutputRefFilePath => this.ProjectInfo.OutputRefFilePath;
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public string DefaultNamespace => this.ProjectInfo.DefaultNamespace;
[DebuggerBrowsable(DebuggerBrowsableState.Collapsed)]
public HostLanguageServices LanguageServices => _languageServices;
......@@ -424,6 +427,16 @@ public ProjectState UpdateOutputRefFilePath(string outputRefFilePath)
return this.With(projectInfo: this.ProjectInfo.WithOutputRefFilePath(outputRefFilePath).WithVersion(this.Version.GetNewerVersion()));
}
public ProjectState UpdateDefaultNamespace(string defaultNamespace)
{
if (defaultNamespace == this.DefaultNamespace)
{
return this;
}
return this.With(projectInfo: this.ProjectInfo.WithDefaultNamespace(defaultNamespace).WithVersion(this.Version.GetNewerVersion()));
}
public ProjectState UpdateCompilationOptions(CompilationOptions options)
{
if (options == this.CompilationOptions)
......
......@@ -304,6 +304,20 @@ public Solution WithProjectOutputRefFilePath(ProjectId projectId, string outputR
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the default namespace.
/// </summary>
internal Solution WithProjectDefaultNamespace(ProjectId projectId, string defaultNamespace)
{
var newState = _state.WithProjectDefaultNamespace(projectId, defaultNamespace);
if (newState == _state)
{
return this;
}
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the name.
/// </summary>
......
......@@ -656,6 +656,29 @@ public SolutionState WithProjectOutputRefFilePath(ProjectId projectId, string ou
return this.ForkProject(newProjectState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the default namespace.
/// </summary>
public SolutionState WithProjectDefaultNamespace(ProjectId projectId, string defaultNamespace)
{
if (projectId == null)
{
throw new ArgumentNullException(nameof(projectId));
}
CheckContainsProject(projectId);
var oldProjectState = this.GetProjectState(projectId);
var newProjectState = oldProjectState.UpdateDefaultNamespace(defaultNamespace);
if (oldProjectState == newProjectState)
{
return this;
}
return this.ForkProject(newProjectState);
}
/// <summary>
/// Creates a new solution instance with the project specified updated to have the name.
/// </summary>
......
......@@ -1445,7 +1445,8 @@ private ProjectInfo CreateProjectInfo(Project project)
project.AdditionalDocuments.Select(d => CreateDocumentInfoWithText(d)),
project.IsSubmission,
project.State.HostObjectType,
project.OutputRefFilePath);
project.OutputRefFilePath)
.WithDefaultNamespace(project.DefaultNamespace);
}
private DocumentInfo CreateDocumentInfoWithText(TextDocument doc)
......
......@@ -267,6 +267,11 @@ private async Task<Project> UpdateProjectInfoAsync(Project project, Checksum inf
project = project.Solution.WithProjectOutputRefFilePath(project.Id, newProjectInfo.OutputRefFilePath).GetProject(project.Id);
}
if (project.State.ProjectInfo.Attributes.DefaultNamespace != newProjectInfo.DefaultNamespace)
{
project = project.Solution.WithProjectDefaultNamespace(project.Id, newProjectInfo.DefaultNamespace).GetProject(project.Id);
}
if (project.State.ProjectInfo.Attributes.HasAllInformation != newProjectInfo.HasAllInformation)
{
project = project.Solution.WithHasAllInformation(project.Id, newProjectInfo.HasAllInformation).GetProject(project.Id);
......@@ -516,7 +521,9 @@ private async Task<ProjectInfo> CreateProjectInfoAsync(Checksum projectChecksum)
projectInfo.Id, projectInfo.Version, projectInfo.Name, projectInfo.AssemblyName,
projectInfo.Language, projectInfo.FilePath, projectInfo.OutputFilePath,
compilationOptions, parseOptions,
documents, p2p, metadata, analyzers, additionals, projectInfo.IsSubmission).WithHasAllInformation(projectInfo.HasAllInformation);
documents, p2p, metadata, analyzers, additionals, projectInfo.IsSubmission)
.WithHasAllInformation(projectInfo.HasAllInformation)
.WithDefaultNamespace(projectInfo.DefaultNamespace);
}
private async Task<List<T>> CreateCollectionAsync<T>(ChecksumCollection collections)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册