Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions src/Build.OM.UnitTests/Instance/ProjectItemInstance_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,37 @@ public void AccessorsWithoutMetadata()
Assert.False(item.Metadata.GetEnumerator().MoveNext());
}

[Fact]
public void LocationTracksOriginatingItemElement()
{
using ProjectRootElementFromString projectRootElementFromString = new(
"""
<Project>
<ItemGroup>
<i Include="i1" />
</ItemGroup>
</Project>
""");

ProjectRootElement xml = projectRootElementFromString.Project;
ProjectItemElement itemElement = xml.Items.Single();
ProjectItemInstance item = new ProjectInstance(xml).GetItems("i").Single();

TaskItemLocation? itemLocation = item.Location;
Assert.True(itemLocation.HasValue);
TaskItemLocation location = itemLocation.Value;
Assert.Equal(itemElement.Location.File, location.File);
Assert.Equal(itemElement.Location.Line, location.Line);
Assert.Equal(itemElement.Location.Column, location.Column);
Assert.Equal(location, ((ITaskItem3)item).Location.Value);

var copiedItem = new Utilities.TaskItem(item);
TaskItemLocation copiedLocation = copiedItem.Location.Value;
Assert.Equal(location.File, copiedLocation.File);
Assert.Equal(location.Line, copiedLocation.Line);
Assert.Equal(location.Column, copiedLocation.Column);
}

/// <summary>
/// Basic ProjectItemInstance with metadata
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Build.UnitTests/Instance/TaskItem_Tests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public class TaskItem_Tests
[Fact]
public void Serialization()
{
TaskItem item = new TaskItem("foo", "bar.proj");
TaskItem item = new TaskItem("foo", "bar.proj", sourceLineNumber: 12, sourceColumnNumber: 34);
item.SetMetadata("a", "b");

TranslationHelpers.GetWriteTranslator().Translate(ref item, TaskItem.FactoryForDeserialization);
Expand All @@ -61,6 +61,7 @@ public void Serialization()
Assert.Equal(item.MetadataCount, deserializedItem.MetadataCount);
Assert.Equal(item.GetMetadata("a"), deserializedItem.GetMetadata("a"));
Assert.Equal(item.GetMetadata(ItemSpecModifiers.DefiningProjectFullPath), deserializedItem.GetMetadata(ItemSpecModifiers.DefiningProjectFullPath));
Assert.Equal(item.Location, deserializedItem.Location);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ namespace Microsoft.Build.Engine.UnitTests.InstanceFromRemote
internal sealed class FakeProjectItemElementLink : ProjectItemElementLink
{
private readonly string _filePath;
private readonly ElementLocation _location;

public FakeProjectItemElementLink(string elementName, string filePath)
{
ElementName = elementName ?? throw new ArgumentNullException(nameof(elementName));
_filePath = filePath ?? throw new ArgumentNullException(nameof(filePath));
_location = ElementLocation.Create(filePath, 1, 1);
}

public override int Count => throw new NotImplementedException();
Expand All @@ -45,7 +47,7 @@ public FakeProjectItemElementLink(string elementName, string filePath)

public override ProjectElement NextSibling => throw new NotImplementedException();

public override ElementLocation Location => throw new NotImplementedException();
public override ElementLocation Location => _location;

public override IReadOnlyCollection<XmlAttributeLink> Attributes => throw new NotImplementedException();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,10 @@ private List<ProjectItemInstance> ExpandItemIntoItems(

// Split Include on any semicolons, and take each split in turn
var includeSplits = ExpressionShredder.SplitSemiColonSeparatedList(evaluatedInclude);
ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(Project, originalItem.ItemType);
ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(Project, originalItem.ItemType)
{
SourceLocation = originalItem.Location,
};

// EngineFileUtilities.GetFileListEscaped api invocation evaluates excludes by default.
// If the code process any expression like "@(x)", we need to handle excludes explicitly using EvaluateExcludePaths().
Expand Down Expand Up @@ -473,7 +476,9 @@ private List<ProjectItemInstance> ExpandItemIntoItems(
null,
null,
originalItem.Location.File,
useItemDefinitionsWithoutModification: false));
useItemDefinitionsWithoutModification: false,
originalItem.Location.Line,
originalItem.Location.Column));
}
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1223,7 +1223,10 @@ private void InferTaskOutputs(
// This is an output item.
// Expand only with properties first, so that expressions like Include="@(foo)" will transfer the metadata of the "foo" items as well, not just their item specs.
var outputItemSpecs = bucket.Expander.ExpandIntoStringListLeaveEscaped(taskParameterAttribute, ExpanderOptions.ExpandPropertiesAndMetadata, taskItemInstance.TaskParameterLocation);
ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(_buildRequestEntry.RequestConfiguration.Project, itemName);
ProjectItemInstanceFactory itemFactory = new ProjectItemInstanceFactory(_buildRequestEntry.RequestConfiguration.Project, itemName)
{
SourceLocation = taskItemInstance.Location,
};

foreach (string outputItemSpec in outputItemSpecs)
{
Expand Down
8 changes: 4 additions & 4 deletions src/Build/BackEnd/TaskExecutionHost/TaskExecutionHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1801,7 +1801,7 @@ private void GatherTaskItemOutputs(bool outputTargetIsItem, string outputTargetN
{
// The common case -- all items involved are Microsoft.Build.Execution.ProjectItemInstance.TaskItems.
// Furthermore, because that is true, we know by definition that they also implement ITaskItem2.
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, outputAsProjectItem.IncludeEscaped, parameterLocationEscaped);
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, outputAsProjectItem.IncludeEscaped, parameterLocationEscaped, parameterLocation.Line, parameterLocation.Column);

newItem.SetMetadata(outputAsProjectItem.MetadataCollection); // copy-on-write!
}
Expand All @@ -1810,7 +1810,7 @@ private void GatherTaskItemOutputs(bool outputTargetIsItem, string outputTargetN
if (output is ITaskItem2 outputAsITaskItem2)
{
// Probably a Microsoft.Build.Utilities.TaskItem. Not quite as good, but we can still preserve escaping.
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, outputAsITaskItem2.EvaluatedIncludeEscaped, parameterLocationEscaped);
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, outputAsITaskItem2.EvaluatedIncludeEscaped, parameterLocationEscaped, parameterLocation.Line, parameterLocation.Column);

// If found, directly pass the backing copy-on-write dictionary.
// Otherwise, retrieve a cloned dictionary from the task item.
Expand All @@ -1830,7 +1830,7 @@ private void GatherTaskItemOutputs(bool outputTargetIsItem, string outputTargetN
{
// Not a ProjectItemInstance.TaskItem or even a ITaskItem2, so we have to fake it.
// Setting an item spec expects the escaped value, as does setting metadata.
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, EscapingUtilities.Escape(output.ItemSpec), parameterLocationEscaped);
newItem = new ProjectItemInstance(_projectInstance, outputTargetName, EscapingUtilities.Escape(output.ItemSpec), parameterLocationEscaped, parameterLocation.Line, parameterLocation.Column);

newItem.SetMetadataOnTaskOutput(EnumerateMetadata(output.CloneCustomMetadata()));

Expand Down Expand Up @@ -1948,7 +1948,7 @@ private void GatherArrayStringAndValueOutputs(bool outputTargetIsItem, string ou
// attempting to put an empty string into an item is a no-op.
if (output?.Length > 0)
{
_batchBucket.Lookup.AddNewItem(new ProjectItemInstance(_projectInstance, outputTargetName, EscapingUtilities.Escape(output), EscapingUtilities.Escape(parameterLocation.File)));
_batchBucket.Lookup.AddNewItem(new ProjectItemInstance(_projectInstance, outputTargetName, EscapingUtilities.Escape(output), EscapingUtilities.Escape(parameterLocation.File), parameterLocation.Line, parameterLocation.Column));
}
}

Expand Down
8 changes: 6 additions & 2 deletions src/Build/Instance/ProjectInstance.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3537,7 +3537,9 @@ private ProjectItemInstance InstantiateProjectItemInstance(ProjectItem item)
directMetadata,
inheritedItemDefinitions,
item.Xml.ContainingProject.EscapedFullPath,
useItemDefinitionsWithoutModification: false);
useItemDefinitionsWithoutModification: false,
item.Xml.Location.Line,
item.Xml.Location.Column);

return instance;
}
Expand Down Expand Up @@ -3597,7 +3599,9 @@ private static ProjectItemInstance InstantiateProjectItemInstanceFromImmutablePr
directMetadata,
inheritedItemDefinitions,
item.Xml.ContainingProject.EscapedFullPath,
useItemDefinitionsWithoutModification: true);
useItemDefinitionsWithoutModification: true,
item.Xml.Location.Line,
item.Xml.Location.Column);
return instance;
}

Expand Down
Loading
Loading