Skip to content
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 14 additions & 0 deletions bicep-types.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DD29C249-759
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Types", "src\Bicep.Types\Bicep.Types.csproj", "{2C152898-5869-453F-8EE1-8B531A031B19}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Types.Validation", "src\Bicep.Types.Validation\Bicep.Types.Validation.csproj", "{6F4E2A1B-9C3D-4A5E-8B7F-0D1C2E3F4A5B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Types.UnitTests", "src\Bicep.Types.UnitTests\Bicep.Types.UnitTests.csproj", "{E5EC02E2-C7BF-48C8-A2E9-DDB3C37F3DFF}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bicep.Types.Validation.UnitTests", "src\Bicep.Types.Validation.UnitTests\Bicep.Types.Validation.UnitTests.csproj", "{7E894B3C-B6EF-4B42-B616-076558A7C446}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -22,13 +26,23 @@ Global
{2C152898-5869-453F-8EE1-8B531A031B19}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2C152898-5869-453F-8EE1-8B531A031B19}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2C152898-5869-453F-8EE1-8B531A031B19}.Release|Any CPU.Build.0 = Release|Any CPU
{6F4E2A1B-9C3D-4A5E-8B7F-0D1C2E3F4A5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F4E2A1B-9C3D-4A5E-8B7F-0D1C2E3F4A5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F4E2A1B-9C3D-4A5E-8B7F-0D1C2E3F4A5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F4E2A1B-9C3D-4A5E-8B7F-0D1C2E3F4A5B}.Release|Any CPU.Build.0 = Release|Any CPU
{E5EC02E2-C7BF-48C8-A2E9-DDB3C37F3DFF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E5EC02E2-C7BF-48C8-A2E9-DDB3C37F3DFF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E5EC02E2-C7BF-48C8-A2E9-DDB3C37F3DFF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E5EC02E2-C7BF-48C8-A2E9-DDB3C37F3DFF}.Release|Any CPU.Build.0 = Release|Any CPU
{7E894B3C-B6EF-4B42-B616-076558A7C446}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7E894B3C-B6EF-4B42-B616-076558A7C446}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7E894B3C-B6EF-4B42-B616-076558A7C446}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7E894B3C-B6EF-4B42-B616-076558A7C446}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{2C152898-5869-453F-8EE1-8B531A031B19} = {DD29C249-759E-4F30-B9B1-A47CB337F870}
{6F4E2A1B-9C3D-4A5E-8B7F-0D1C2E3F4A5B} = {DD29C249-759E-4F30-B9B1-A47CB337F870}
{E5EC02E2-C7BF-48C8-A2E9-DDB3C37F3DFF} = {DD29C249-759E-4F30-B9B1-A47CB337F870}
{7E894B3C-B6EF-4B42-B616-076558A7C446} = {DD29C249-759E-4F30-B9B1-A47CB337F870}
EndGlobalSection
EndGlobal
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<None Remove="Files/**/*.*" />
<EmbeddedResource Include="Files/**/*.*" LogicalName="$([System.String]::new('Files/%(RecursiveDir)%(Filename)%(Extension)').Replace('\', '/'))" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Bicep.Types.Validation\Bicep.Types.Validation.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="System.Memory.Data" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using Azure.Bicep.Types.Validation.Diagnostics;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Azure.Bicep.Types.Validation.UnitTests.Diagnostics;

[TestClass]
public class TypePackageValidationResultTests
{
[TestMethod]
public void Result_is_valid_when_there_are_no_error_diagnostics()
{
var result = Create(new[] { Warning() }, new TypePackageValidationOptions());

result.IsValid.Should().BeTrue();
}

[TestMethod]
public void Result_is_invalid_when_an_error_diagnostic_exists()
{
var result = Create(new[] { Error() }, new TypePackageValidationOptions());

result.IsValid.Should().BeFalse();
}

[TestMethod]
public void Summary_counts_all_detected_diagnostics_before_filtering_and_truncation()
{
var options = new TypePackageValidationOptions
{
IncludeWarnings = false,
IncludeInformationalDiagnostics = false,
MaxDiagnostics = 1,
};

var result = Create(new[] { Error(), Warning(), Information() }, options);

result.Summary.ErrorCount.Should().Be(1);
result.Summary.WarningCount.Should().Be(1);
result.Summary.InfoCount.Should().Be(1);
}

[TestMethod]
public void IsValid_reflects_detected_errors_even_when_filtered_or_truncated()
{
var options = new TypePackageValidationOptions { MaxDiagnostics = 1 };

var result = Create(new[] { Error(), Warning(), Warning() }, options);

result.IsValid.Should().BeFalse();
}

[TestMethod]
public void Warning_filtering_affects_only_returned_diagnostics()
{
var options = new TypePackageValidationOptions { IncludeWarnings = false };

var result = Create(new[] { Warning(), Error() }, options);

result.Diagnostics.Should().OnlyContain(d => d.Severity != TypeValidationDiagnosticSeverity.Warning);
result.Summary.WarningCount.Should().Be(1);
}

[TestMethod]
public void Informational_diagnostics_are_excluded_by_default_but_counted()
{
var result = Create(new[] { Information() }, new TypePackageValidationOptions());

result.Diagnostics.Should().BeEmpty();
result.Summary.InfoCount.Should().Be(1);
}

[TestMethod]
public void Informational_diagnostics_are_returned_when_requested()
{
var options = new TypePackageValidationOptions { IncludeInformationalDiagnostics = true };

var result = Create(new[] { Information() }, options);

result.Diagnostics.Should().ContainSingle();
}

[TestMethod]
public void Null_max_diagnostics_applies_no_cap()
{
var options = new TypePackageValidationOptions { MaxDiagnostics = null };

var result = Create(new[] { Error("BCPVT100", "a.json"), Error("BCPVT101", "b.json"), Error("BCPVT102", "c.json") }, options);

result.DiagnosticsTruncated.Should().BeFalse();
result.Diagnostics.Should().HaveCount(3);
}

[TestMethod]
public void Positive_max_diagnostics_truncates_and_sets_flag()
{
var options = new TypePackageValidationOptions { MaxDiagnostics = 2 };

var result = Create(new[] { Error("BCPVT100", "a.json"), Error("BCPVT101", "b.json"), Error("BCPVT102", "c.json") }, options);

result.DiagnosticsTruncated.Should().BeTrue();
result.Diagnostics.Should().HaveCount(2);
result.Summary.ErrorCount.Should().Be(3);
}

[TestMethod]
public void Max_diagnostics_not_exceeded_leaves_truncation_false()
{
var options = new TypePackageValidationOptions { MaxDiagnostics = 5 };

var result = Create(new[] { Error("BCPVT100", "a.json"), Error("BCPVT101", "b.json") }, options);

result.DiagnosticsTruncated.Should().BeFalse();
result.Diagnostics.Should().HaveCount(2);
}

[TestMethod]
public void Returned_diagnostics_are_sorted()
{
var later = new TypeValidationDiagnostic("BCPVT200", TypeValidationDiagnosticSeverity.Error, "b", path: "types.json", line: 2, column: 1);
var earlier = new TypeValidationDiagnostic("BCPVT100", TypeValidationDiagnosticSeverity.Error, "a", path: "index.json", line: 1, column: 1);

var result = Create(new[] { later, earlier }, new TypePackageValidationOptions());

result.Diagnostics[0].Should().BeSameAs(earlier);
result.Diagnostics[1].Should().BeSameAs(later);
}

private static TypePackageValidationResult Create(
IEnumerable<TypeValidationDiagnostic> diagnostics,
TypePackageValidationOptions options)
=> TypePackageValidationResult.Create(TypePackageValidationMode.CanonicalWriter, diagnostics, options);

private static TypeValidationDiagnostic Error(string code = "BCPVT001", string? path = null)
=> new(code, TypeValidationDiagnosticSeverity.Error, "error", path: path);

private static TypeValidationDiagnostic Warning(string code = "BCPVT500", string? path = null)
=> new(code, TypeValidationDiagnosticSeverity.Warning, "warning", path: path);

private static TypeValidationDiagnostic Information(string code = "BCPVT900", string? path = null)
=> new(code, TypeValidationDiagnosticSeverity.Info, "info", path: path);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections.Generic;
using Azure.Bicep.Types.Validation.Diagnostics;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Azure.Bicep.Types.Validation.UnitTests.Diagnostics;

[TestClass]
public class TypeValidationDiagnosticTests
{
[TestMethod]
public void Diagnostics_sort_deterministically_by_path_line_column_and_code()
{
var typesLater = Diag("BCPVT200", path: "types.json", line: 5, column: 2);
var typesLaterHigherCode = Diag("BCPVT201", path: "types.json", line: 5, column: 2);
var indexFirst = Diag("BCPVT100", path: "index.json", line: 1, column: 1);

var list = new List<TypeValidationDiagnostic> { typesLater, typesLaterHigherCode, indexFirst };
list.Sort(TypeValidationDiagnosticComparer.Instance);

list.Should().ContainInOrder(indexFirst, typesLater, typesLaterHigherCode);
}

[TestMethod]
public void Input_level_diagnostics_sort_before_file_level_diagnostics()
{
var inputLevel = Diag("BCPVT001", path: null);
var fileLevel = Diag("BCPVT100", path: "index.json", line: 1, column: 1);

var list = new List<TypeValidationDiagnostic> { fileLevel, inputLevel };
list.Sort(TypeValidationDiagnosticComparer.Instance);

list[0].Should().BeSameAs(inputLevel);
list[1].Should().BeSameAs(fileLevel);
}

[TestMethod]
public void Related_locations_are_preserved()
{
var related = new TypeValidationDiagnosticRelatedLocation(
message: "declared here",
path: "types.json",
jsonPointer: "/0",
line: 3,
column: 1);

var diagnostic = new TypeValidationDiagnostic(
"BCPVT400",
TypeValidationDiagnosticSeverity.Error,
"wrong target kind",
path: "index.json",
relatedLocations: new[] { related });

diagnostic.RelatedLocations.Should().ContainSingle()
.Which.Message.Should().Be("declared here");
}

[TestMethod]
public void Diagnostic_defaults_to_no_related_locations()
{
var diagnostic = new TypeValidationDiagnostic(
"BCPVT100",
TypeValidationDiagnosticSeverity.Error,
"message");

diagnostic.RelatedLocations.Should().BeEmpty();
}

private static TypeValidationDiagnostic Diag(string code, string? path, int? line = null, int? column = null)
=> new(code, TypeValidationDiagnosticSeverity.Error, $"message for {code}", path: path, line: line, column: column);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"isValid": false,
"mode": "canonicalWriter",
"diagnostics": [
{
"code": "BCPVT022",
"severity": "error",
"message": "BuiltInType.kind at \u0027/2/kind\u0027 in \u0027types.json\u0027 uses reserved legacy built-in kind 8 (\u0027ResourceRef\u0027), which CanonicalWriter packages must not emit.",
"path": "types.json",
"jsonPointer": "/2/kind",
"line": 16,
"column": 5
}
],
"diagnosticsTruncated": false,
"summary": {
"errorCount": 1,
"warningCount": 0,
"infoCount": 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"isValid": true,
"mode": "compatibleReader",
"diagnostics": [
{
"code": "BCPVT023",
"severity": "warning",
"message": "BuiltInType.kind at \u0027/2/kind\u0027 in \u0027types.json\u0027 uses reserved legacy built-in kind 8 (\u0027ResourceRef\u0027), accepted only for CompatibleReader mode.",
"path": "types.json",
"jsonPointer": "/2/kind",
"line": 16,
"column": 5
}
],
"diagnosticsTruncated": false,
"summary": {
"errorCount": 0,
"warningCount": 1,
"infoCount": 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"resources": { "My.Rp/x@2026-01-01": { "$ref": "types.json#/0" } },
"resourceFunctions": {},
"namespaceFunctions": []
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
[
{
"$type": "ResourceType",
"name": "My.Rp/x@2026-01-01",
"body": { "$ref": "#/1" },
"readableScopes": 8,
"writableScopes": 8
},
{
"$type": "ObjectType",
"name": "body",
"properties": {}
},
{
"$type": "BuiltInType",
"kind": 8
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"name":"builtin-type-resource-ref","description":"A BuiltInType uses the reserved legacy kind 8 (ResourceRef), which has no canonical replacement: rejected in CanonicalWriter, accepted with a warning in CompatibleReader.","category":"compatibility","modes":["canonicalWriter","compatibleReader"]}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"isValid": false,
"mode": "canonicalWriter",
"diagnostics": [
{
"code": "BCPVT022",
"severity": "error",
"message": "BuiltInType.kind at \u0027/2/kind\u0027 in \u0027types.json\u0027 uses legacy built-in kind 5 (\u0027String\u0027). CanonicalWriter packages must use \u0027StringType\u0027.",
"path": "types.json",
"jsonPointer": "/2/kind",
"line": 16,
"column": 5
}
],
"diagnosticsTruncated": false,
"summary": {
"errorCount": 1,
"warningCount": 0,
"infoCount": 0
}
}
Loading
Loading