Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="Sarif.Sdk" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,5 @@
<ItemGroup>
<PackageReference Include="Azure.Bicep.Core" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
</Project>
71 changes: 40 additions & 31 deletions src/Analyzer.BicepProcessor/BicepTemplateProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Licensed under the MIT License.

using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;
Expand All @@ -15,14 +16,18 @@
using Bicep.Core.Extensions;
using Bicep.Core.Features;
using Bicep.Core.FileSystem;
using Bicep.Core.Navigation;
using Bicep.Core.Registry;
using Bicep.Core.Registry.Auth;
using Bicep.Core.Registry.PublicRegistry;
using Bicep.Core.Semantics.Namespaces;
using Bicep.Core.Syntax;
using Bicep.Core.Text;
using Bicep.Core.TypeSystem.Providers;
using Bicep.Core.Utils;
using Bicep.Core.Workspaces;
using Bicep.IO.Abstraction;
using Bicep.IO.FileSystem;
using Microsoft.Extensions.DependencyInjection;
using BicepEnvironment = Bicep.Core.Utils.Environment;
using IOFileSystem = System.IO.Abstractions.FileSystem;
Expand All @@ -41,14 +46,16 @@ public static class BicepTemplateProcessor
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddBicepCore(this IServiceCollection services) => services
.AddSingleton<INamespaceProvider, DefaultNamespaceProvider>()
.AddSingleton<INamespaceProvider, NamespaceProvider>()
.AddSingleton<IResourceTypeProviderFactory, ResourceTypeProviderFactory>()
.AddSingleton<IContainerRegistryClientFactory, ContainerRegistryClientFactory>()
.AddSingleton<IPublicRegistryModuleMetadataProvider, PublicRegistryModuleMetadataProvider>()
.AddSingleton<ITemplateSpecRepositoryFactory, TemplateSpecRepositoryFactory>()
.AddSingleton<IModuleDispatcher, ModuleDispatcher>()
.AddSingleton<IArtifactRegistryProvider, DefaultArtifactRegistryProvider>()
.AddSingleton<ITokenCredentialFactory, TokenCredentialFactory>()
.AddSingleton<IFileResolver, FileResolver>()
.AddSingleton<IFileExplorer, FileSystemFileExplorer>()
.AddSingleton<IEnvironment, BicepEnvironment>()
.AddSingleton<IFileSystem, IOFileSystem>()
.AddSingleton<IConfigurationManager, ConfigurationManager>()
Expand Down Expand Up @@ -91,47 +98,49 @@ public static (string, BicepMetadata) ConvertBicepToJson(string bicepPath)
throw new Exception($"Bicep issues found:{SysEnvironment.NewLine}{string.Join(SysEnvironment.NewLine, bicepIssues)}");
}

string GetPathRelativeToEntryPoint(string absolutePath) => Path.GetRelativePath(
Path.GetDirectoryName(compilation.SourceFileGrouping.EntryPoint.FileUri.AbsolutePath), absolutePath);

// Collect all needed module info from SourceFileGrouping metadata
var moduleInfo = compilation.SourceFileGrouping.FileUriResultByArtifactReference.Select(sourceFileAndMetadata =>
{
var bicepSourceFile = sourceFileAndMetadata.Key as BicepSourceFile;
var pathRelativeToEntryPoint = GetPathRelativeToEntryPoint(bicepSourceFile.FileUri.AbsolutePath);
var modules = sourceFileAndMetadata.Value
.Select(artifactRefAndUriResult =>
string entryPointDirectory = Path.GetDirectoryName(compilation.SourceFileGrouping.EntryPoint.Uri.AbsolutePath);

bool IsResolvedLocalModuleReference(KeyValuePair<IArtifactReferenceSyntax, ArtifactResolutionInfo> artifact) =>
// Only include local module references (not modules imported from public/private registries, i.e. those that match IsModuleRegistryPathRegex),
// as it is more useful for user to see line number of the module declaration itself,
// rather than the line number in the module (as the user does not control the template in the registry directly).
artifact.Key is ModuleDeclarationSyntax moduleDeclaration &&
moduleDeclaration.Path is StringSyntax moduleDeclarationPath &&
!moduleDeclarationPath.SegmentValues.Any(IsModuleRegistryPathRegex.IsMatch) &&
artifact.Value.Result.IsSuccess();

// Create SourceFileModuleInfo collection by gathering all needed module info from SourceFileGrouping metadata.
// Group by the source file path to allow for easy construction of SourceFileModuleInfo.
var moduleInfo = compilation.SourceFileGrouping.ArtifactLookup
.Where(IsResolvedLocalModuleReference)
.GroupBy(artifact => artifact.Value.Origin)
.Select(grouping =>
{
var bicepSourceFile = grouping.Key;
var pathRelativeToEntryPoint = Path.GetRelativePath(
Path.GetDirectoryName(compilation.SourceFileGrouping.EntryPoint.Uri.AbsolutePath), bicepSourceFile.Uri.AbsolutePath);

// Use the grouping value (KeyValuePair<IArtifactReferenceSyntax,ArtifactResolutionInfo>) to create
// a dictionary of module line numbers to file paths.
// This represents the modules in the source file, and where (what lines) they are referenced.
var modules = grouping.Select(artifactRefAndUriResult =>
{
// Do not include modules imported from public/private registries, as it is more useful for user to see line number
// of the module declaration itself instead of line number in the module as the user does not control template in registry directly
if (artifactRefAndUriResult.Key is not ModuleDeclarationSyntax moduleDeclaration
|| moduleDeclaration.Path is not StringSyntax moduleDeclarationPath
|| moduleDeclarationPath.SegmentValues.Any(v => IsModuleRegistryPathRegex.IsMatch(v)))
{
return null;
}

if (!artifactRefAndUriResult.Value.IsSuccess())
{
return null;
}

var moduleLine = TextCoordinateConverter.GetPosition(bicepSourceFile.LineStarts, moduleDeclaration.Span.Position).line;
var modulePath = new FileInfo(artifactRefAndUriResult.Value.Unwrap().AbsolutePath).FullName; // converts path to current platform
var module = artifactRefAndUriResult.Key as ModuleDeclarationSyntax;
var moduleLine = TextCoordinateConverter.GetPosition(bicepSourceFile.LineStarts, module.Span.Position).line;
var modulePath = new FileInfo(artifactRefAndUriResult.Value.Result.Unwrap().AbsolutePath).FullName; // converts path to current platform

// Use relative paths for bicep to match file paths used in bicep modules and source map
if (modulePath.EndsWith(".bicep"))
{
modulePath = GetPathRelativeToEntryPoint(modulePath);
modulePath = Path.GetRelativePath(entryPointDirectory, modulePath);
}

return new { moduleLine, modulePath };
})
.WhereNotNull()
.ToDictionary(c => c.moduleLine, c => c.modulePath);

return new SourceFileModuleInfo(pathRelativeToEntryPoint, modules);
});
return new SourceFileModuleInfo(pathRelativeToEntryPoint, modules);
});

var bicepMetadata = new BicepMetadata()
{
Expand Down
23 changes: 12 additions & 11 deletions src/Analyzer.BicepProcessor/SourceMapFeatureProvider.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using Azure.ResourceManager.Resources;
using Bicep.Core.Features;
using Bicep.IO.Abstraction;

namespace Microsoft.Azure.Templates.Analyzer.BicepProcessor
{
Expand Down Expand Up @@ -39,7 +40,7 @@ public SourceMapFeatureProvider(IFeatureProvider features)
public string AssemblyVersion => features.AssemblyVersion;

/// <inheritdoc/>
public string CacheRootDirectory => features.CacheRootDirectory;
public IDirectoryHandle CacheRootDirectory => features.CacheRootDirectory;

/// <inheritdoc/>
public bool SymbolicNameCodegenEnabled => features.SymbolicNameCodegenEnabled;
Expand All @@ -54,33 +55,33 @@ public SourceMapFeatureProvider(IFeatureProvider features)
public bool SourceMappingEnabled => true;

/// <inheritdoc/>
public bool UserDefinedFunctionsEnabled => features.UserDefinedFunctionsEnabled;
public bool TestFrameworkEnabled => features.TestFrameworkEnabled;

/// <inheritdoc/>
public bool DynamicTypeLoadingEnabled => features.DynamicTypeLoadingEnabled;
public bool AssertsEnabled => features.AssertsEnabled;

/// <inheritdoc/>
public bool PrettyPrintingEnabled => features.PrettyPrintingEnabled;
public bool OptionalModuleNamesEnabled => features.OptionalModuleNamesEnabled;

/// <inheritdoc/>
public bool TestFrameworkEnabled => features.TestFrameworkEnabled;
public bool ResourceDerivedTypesEnabled => features.ResourceDerivedTypesEnabled;

/// <inheritdoc/>
public bool AssertsEnabled => features.AssertsEnabled;
public bool LegacyFormatterEnabled => features.LegacyFormatterEnabled;

/// <inheritdoc/>
public bool MicrosoftGraphPreviewEnabled => features.MicrosoftGraphPreviewEnabled;
public bool LocalDeployEnabled => features.LocalDeployEnabled;

/// <inheritdoc/>
public bool PublishSourceEnabled => features.PublishSourceEnabled;
public bool ExtendableParamFilesEnabled => features.ExtendableParamFilesEnabled;

/// <inheritdoc/>
public bool ProviderRegistryEnabled => features.ProviderRegistryEnabled;
public bool SecureOutputsEnabled => features.SecureOutputsEnabled;

/// <inheritdoc/>
public bool OptionalModuleNamesEnabled => features.OptionalModuleNamesEnabled;
public bool ResourceInfoCodegenEnabled => features.ResourceInfoCodegenEnabled;

/// <inheritdoc/>
public bool ResourceDerivedTypesEnabled => features.ResourceDerivedTypesEnabled;
public bool ExtensibilityV2EmittingEnabled => features.ExtensibilityV2EmittingEnabled;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Analyzer.Core\Analyzer.Core.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Analyzer.Core\Analyzer.Core.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Analyzer.JsonRuleEngine\Analyzer.JsonRuleEngine.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
<PackageReference Include="Moq" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Analyzer.JsonRuleEngine\Analyzer.JsonRuleEngine.csproj" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public class ExistsOperatorTests
[DataRow(true, DisplayName = "Property value is a boolean")]
[DataRow(1, DisplayName = "Property value is an integer")]
[DataRow(0.1, DisplayName = "Property value is a float")]
[DataRow(new object[] { }, DisplayName = "Property value is an array")]
[DataRow((object)(new object[] { }), DisplayName = "Property value is an array")]
[DataRow(null, DisplayName = "Property value is null")]
public void EvaluateExpression_PropertyExists_ExistsExpressionIsTrue(object jTokenValue)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,9 @@ void ValidateExpression(ExpressionDefinition expression, Type expectedSpecificTy
}

[DataTestMethod]
[DataRow(DisplayName = "Not Expression")]
[DataRow("not", typeof(NotExpressionDefinition), DisplayName = "Not Expression with Where condition")]
public void ReadJson_ValidNotExpression_ReturnsCorrectTypeAndValues(params object[] whereCondition)
[DataRow(new object[] { }, DisplayName = "Not Expression")]
[DataRow(new object[] { "not", typeof(NotExpressionDefinition) }, DisplayName = "Not Expression with Where condition")]
public void ReadJson_ValidNotExpression_ReturnsCorrectTypeAndValues(object[] whereCondition)
{
var expressionTemplate = $@"
{{
Expand Down Expand Up @@ -313,10 +313,10 @@ public void ReadJson_StructuredExpressionWithInvalidExpression_ThrowsParsingExce
}

[TestMethod]
[DataRow(DisplayName = "No operators")]
[DataRow("hasValue", true, "exists", true, DisplayName = "HasValue and Exists")]
[DataRow(new object[] { }, DisplayName = "No operators")]
[DataRow(["hasValue", true, "exists", true], DisplayName = "HasValue and Exists")]
[ExpectedException(typeof(JsonSerializationException))]
public void ReadJson_LeafWithInvalidOperatorCount_ThrowsParsingException(params object[] operators)
public void ReadJson_LeafWithInvalidOperatorCount_ThrowsParsingException(object[] operators)
{
var leafDefinition = "{\"resourceType\": \"resource\", \"path\": \"path\"";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class HasValueOperatorTests
[DataRow(true, DisplayName = "Property value is a boolean")]
[DataRow(1, DisplayName = "Property value is an integer")]
[DataRow(0.1, DisplayName = "Property value is a float")]
[DataRow(new object[] { }, DisplayName = "Property value is an array")]
[DataRow((object)(new object[] { }), DisplayName = "Property value is an array")]
public void EvaluateExpression_PropertyHasValue_HasValueIsTrue(object jTokenValue)
{
var jToken = TestUtilities.ToJToken(jTokenValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Sarif.Sdk" />
<PackageReference Include="System.IO.Abstractions" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public static void AssemblyInitialize(TestContext context)
[DataRow("template_and_resource_level_results.json", true, 14, new int[] { 1, 1, 1, 1, 8, 11, 14, 17, 1, 17, 17, 1, 17, 1 }, DisplayName = "Running all the rules against a template with errors reported in both analysis stages")]
[DataRow("template_and_resource_level_results.json", false, 5, new int[] { 11, 17, 17, 17, 17 }, DisplayName = "Running only the security rules against a template with errors reported in both analysis stages")]
// TODO add test case for error, warning (rule with severity level of warning?) and informational (also rule with that severity level?)
public void AnalyzeTemplate_ValidTemplate_ReturnsExpectedEvaluations(string templateFileName, bool runsAllRules, int expectedErrorCount, dynamic expectedLineNumbers)
public void AnalyzeTemplate_ValidTemplate_ReturnsExpectedEvaluations(string templateFileName, bool runsAllRules, int expectedErrorCount, int[] expectedLineNumbers)
{
Assert.AreEqual(expectedErrorCount, expectedLineNumbers.Length);

Expand Down Expand Up @@ -97,7 +97,7 @@ public void AnalyzeTemplate_ValidTemplate_ReturnsExpectedEvaluations(string temp
Assert.AreEqual(expectedErrorCount, failedEvaluations.Count);

// PSRule evaluations can change order depending on the OS:
foreach (var expectedLineNumber in expectedLineNumbers)
foreach (int expectedLineNumber in expectedLineNumbers)
{
var matchingEvaluation = failedEvaluations.Find(evaluation => evaluation.Result.SourceLocation.LineNumber == expectedLineNumber);
failedEvaluations.Remove(matchingEvaluation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
<PackageReference Include="Moq" />
<PackageReference Include="MSTest.TestAdapter" />
<PackageReference Include="MSTest.TestFramework" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="coverlet.collector">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Analyzer.Core\Analyzer.Core.csproj" />
Expand Down
Loading
Loading