From 632fa2d86ef1d7da61029ab49a59de6e27a4b1f9 Mon Sep 17 00:00:00 2001 From: Anthony Martin Date: Thu, 8 Jul 2021 20:08:41 -0400 Subject: [PATCH] Add support for JSON literal string conversion (#3510) * Add support for JSON literal string conversion * Address comments * Add test for unparseable JSON, JSON with comments * Use TryFromJson --- .../main.bicep | 2 +- .../azurefirewall-create-with-zones/main.json | 4 +- .../201/private-aks-cluster/aks.bicep | 2 +- .../examples/201/private-aks-cluster/aks.json | 4 +- .../201/private-aks-cluster/jumpbox.bicep | 2 +- .../201/private-aks-cluster/jumpbox.json | 4 +- .../201/private-aks-cluster/main.json | 10 +- .../201/wvd-create-hostpool/main.json | 26 ++--- .../modules/managedDisks-customimagevm.bicep | 4 +- .../modules/managedDisks-customimagevm.json | 6 +- .../modules/managedDisks-customvhdvm.bicep | 4 +- .../modules/managedDisks-customvhdvm.json | 6 +- .../modules/managedDisks-galleryvm.bicep | 4 +- .../modules/managedDisks-galleryvm.json | 6 +- .../modules/unmanagedDisks-customvhdvm.bicep | 4 +- .../modules/unmanagedDisks-customvhdvm.json | 6 +- .../containergroups.bicep | 4 +- .../containergroups.json | 6 +- .../main.json | 8 +- .../azfw.bicep | 2 +- .../TypeSystem/TypeValidationTests.cs | 68 ++++++++++++ .../LoadFunctions_CRLF/Assets/test.json.txt | 11 ++ .../Files/LoadFunctions_CRLF/main.bicep | 7 ++ .../LoadFunctions_CRLF/main.diagnostics.bicep | 12 ++ .../LoadFunctions_CRLF/main.formatted.bicep | 7 ++ .../Files/LoadFunctions_CRLF/main.json | 10 +- .../LoadFunctions_CRLF/main.symbols.bicep | 13 +++ .../LoadFunctions_CRLF/main.syntax.bicep | 103 +++++++++++++++++- .../LoadFunctions_CRLF/main.tokens.bicep | 60 +++++++++- .../Configuration/ConfigHelperExtensions.cs | 1 - .../Diagnostics/DiagnosticBuilder.cs | 5 + .../Namespaces/SystemNamespaceSymbol.cs | 49 ++++++++- 32 files changed, 397 insertions(+), 63 deletions(-) create mode 100644 src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/Assets/test.json.txt diff --git a/docs/examples/101/azurefirewall-create-with-zones/main.bicep b/docs/examples/101/azurefirewall-create-with-zones/main.bicep index 5ed070ce228..584aebe3646 100644 --- a/docs/examples/101/azurefirewall-create-with-zones/main.bicep +++ b/docs/examples/101/azurefirewall-create-with-zones/main.bicep @@ -45,7 +45,7 @@ resource publicIP 'Microsoft.Network/publicIPAddresses@2020-06-01' = { resource firewall 'Microsoft.Network/azureFirewalls@2020-06-01' = { name: firewallName location: location - zones: length(availabilityZones) == 0 ? json('null') : availabilityZones + zones: length(availabilityZones) == 0 ? null : availabilityZones properties: { ipConfigurations: [ { diff --git a/docs/examples/101/azurefirewall-create-with-zones/main.json b/docs/examples/101/azurefirewall-create-with-zones/main.json index 13d7bc1687d..9932d59a15c 100644 --- a/docs/examples/101/azurefirewall-create-with-zones/main.json +++ b/docs/examples/101/azurefirewall-create-with-zones/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "16439774586836419118" + "templateHash": "1241648137071561964" } }, "parameters": { @@ -85,7 +85,7 @@ "apiVersion": "2020-06-01", "name": "[parameters('firewallName')]", "location": "[parameters('location')]", - "zones": "[if(equals(length(parameters('availabilityZones')), 0), json('null'), parameters('availabilityZones'))]", + "zones": "[if(equals(length(parameters('availabilityZones')), 0), null(), parameters('availabilityZones'))]", "properties": { "ipConfigurations": [ { diff --git a/docs/examples/201/private-aks-cluster/aks.bicep b/docs/examples/201/private-aks-cluster/aks.bicep index 295e88a7fde..d53b0ea298c 100644 --- a/docs/examples/201/private-aks-cluster/aks.bicep +++ b/docs/examples/201/private-aks-cluster/aks.bicep @@ -231,7 +231,7 @@ resource aksCluster 'Microsoft.ContainerService/managedClusters@2021-02-01' = { dockerBridgeCidr: aksClusterDockerBridgeCidr loadBalancerSku: aksClusterLoadBalancerSku } - aadProfile: (aadEnabled ? aadProfileConfiguration : json('null')) + aadProfile: (aadEnabled ? aadProfileConfiguration : null) apiServerAccessProfile: { enablePrivateCluster: aksClusterEnablePrivateCluster } diff --git a/docs/examples/201/private-aks-cluster/aks.json b/docs/examples/201/private-aks-cluster/aks.json index b8b3a6dd9ce..64b14e4fc37 100644 --- a/docs/examples/201/private-aks-cluster/aks.json +++ b/docs/examples/201/private-aks-cluster/aks.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "11819378221515304822" + "templateHash": "5882575949678654988" } }, "parameters": { @@ -389,7 +389,7 @@ "dockerBridgeCidr": "[parameters('aksClusterDockerBridgeCidr')]", "loadBalancerSku": "[parameters('aksClusterLoadBalancerSku')]" }, - "aadProfile": "[if(parameters('aadEnabled'), variables('aadProfileConfiguration'), json('null'))]", + "aadProfile": "[if(parameters('aadEnabled'), variables('aadProfileConfiguration'), null())]", "apiServerAccessProfile": { "enablePrivateCluster": "[parameters('aksClusterEnablePrivateCluster')]" } diff --git a/docs/examples/201/private-aks-cluster/jumpbox.bicep b/docs/examples/201/private-aks-cluster/jumpbox.bicep index cb8981b62e4..781b64a013b 100644 --- a/docs/examples/201/private-aks-cluster/jumpbox.bicep +++ b/docs/examples/201/private-aks-cluster/jumpbox.bicep @@ -133,7 +133,7 @@ resource virtualMachines 'Microsoft.Compute/virtualMachines@2020-12-01' = { computerName: vmName adminUsername: vmAdminUsername adminPassword: vmAdminPasswordOrKey - linuxConfiguration: ((authenticationType == 'password') ? json('null') : linuxConfiguration) + linuxConfiguration: ((authenticationType == 'password') ? null : linuxConfiguration) } storageProfile: { imageReference: { diff --git a/docs/examples/201/private-aks-cluster/jumpbox.json b/docs/examples/201/private-aks-cluster/jumpbox.json index aa80d1c6c01..ca6c3902b1d 100644 --- a/docs/examples/201/private-aks-cluster/jumpbox.json +++ b/docs/examples/201/private-aks-cluster/jumpbox.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "10939577128460965414" + "templateHash": "6673211936035085652" } }, "parameters": { @@ -217,7 +217,7 @@ "computerName": "[parameters('vmName')]", "adminUsername": "[parameters('vmAdminUsername')]", "adminPassword": "[parameters('vmAdminPasswordOrKey')]", - "linuxConfiguration": "[if(equals(parameters('authenticationType'), 'password'), json('null'), variables('linuxConfiguration'))]" + "linuxConfiguration": "[if(equals(parameters('authenticationType'), 'password'), null(), variables('linuxConfiguration'))]" }, "storageProfile": { "copy": [ diff --git a/docs/examples/201/private-aks-cluster/main.json b/docs/examples/201/private-aks-cluster/main.json index 6e053c7e4c5..057fea2acd4 100644 --- a/docs/examples/201/private-aks-cluster/main.json +++ b/docs/examples/201/private-aks-cluster/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "5568127674592377707" + "templateHash": "10839339689322770047" } }, "parameters": { @@ -1076,7 +1076,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "10939577128460965414" + "templateHash": "6673211936035085652" } }, "parameters": { @@ -1288,7 +1288,7 @@ "computerName": "[parameters('vmName')]", "adminUsername": "[parameters('vmAdminUsername')]", "adminPassword": "[parameters('vmAdminPasswordOrKey')]", - "linuxConfiguration": "[if(equals(parameters('authenticationType'), 'password'), json('null'), variables('linuxConfiguration'))]" + "linuxConfiguration": "[if(equals(parameters('authenticationType'), 'password'), null(), variables('linuxConfiguration'))]" }, "storageProfile": { "copy": [ @@ -1591,7 +1591,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "11819378221515304822" + "templateHash": "5882575949678654988" } }, "parameters": { @@ -1975,7 +1975,7 @@ "dockerBridgeCidr": "[parameters('aksClusterDockerBridgeCidr')]", "loadBalancerSku": "[parameters('aksClusterLoadBalancerSku')]" }, - "aadProfile": "[if(parameters('aadEnabled'), variables('aadProfileConfiguration'), json('null'))]", + "aadProfile": "[if(parameters('aadEnabled'), variables('aadProfileConfiguration'), null())]", "apiServerAccessProfile": { "enablePrivateCluster": "[parameters('aksClusterEnablePrivateCluster')]" } diff --git a/docs/examples/201/wvd-create-hostpool/main.json b/docs/examples/201/wvd-create-hostpool/main.json index 76e532f1a05..a2766c79769 100644 --- a/docs/examples/201/wvd-create-hostpool/main.json +++ b/docs/examples/201/wvd-create-hostpool/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "5336278593853148904" + "templateHash": "13154155776809438883" } }, "parameters": { @@ -840,7 +840,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "16380997742108041127" + "templateHash": "12159192052625478905" } }, "parameters": { @@ -1162,7 +1162,7 @@ } ], "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), json('null'), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" + "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), null(), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" }, "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'NSG-linkedTemplate')]" @@ -1185,7 +1185,7 @@ "hardwareProfile": { "vmSize": "[parameters('rdshVmSize')]" }, - "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), json('null'))]", + "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), null())]", "osProfile": { "computerName": "[concat(parameters('rdshPrefix'), add(range(0, parameters('rdshNumberOfInstances'))[copyIndex()], parameters('vmInitialNumber')))]", "adminUsername": "[variables('vmAdministratorUsername')]", @@ -1528,7 +1528,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "3622114365539263083" + "templateHash": "3610555063404175184" } }, "parameters": { @@ -1833,7 +1833,7 @@ } ], "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), json('null'), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" + "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), null(), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" }, "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'NSG-linkedTemplate')]" @@ -1856,7 +1856,7 @@ "hardwareProfile": { "vmSize": "[parameters('rdshVmSize')]" }, - "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), json('null'))]", + "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), null())]", "osProfile": { "computerName": "[concat(parameters('rdshPrefix'), add(range(0, parameters('rdshNumberOfInstances'))[copyIndex()], parameters('vmInitialNumber')))]", "adminUsername": "[variables('vmAdministratorUsername')]", @@ -2201,7 +2201,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "13456880223628676085" + "templateHash": "11204922523232167930" } }, "parameters": { @@ -2505,7 +2505,7 @@ } ], "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), json('null'), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" + "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), null(), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" }, "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'NSG-linkedTemplate')]" @@ -2528,7 +2528,7 @@ "hardwareProfile": { "vmSize": "[parameters('rdshVmSize')]" }, - "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), json('null'))]", + "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), null())]", "osProfile": { "computerName": "[concat(parameters('rdshPrefix'), add(range(0, parameters('rdshNumberOfInstances'))[copyIndex()], parameters('vmInitialNumber')))]", "adminUsername": "[variables('vmAdministratorUsername')]", @@ -2870,7 +2870,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "3774652722909324444" + "templateHash": "1641105856964782992" } }, "parameters": { @@ -3174,7 +3174,7 @@ } ], "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), json('null'), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" + "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), null(), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" }, "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'NSG-linkedTemplate')]" @@ -3197,7 +3197,7 @@ "hardwareProfile": { "vmSize": "[parameters('rdshVmSize')]" }, - "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), json('null'))]", + "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), null())]", "osProfile": { "computerName": "[concat(parameters('rdshPrefix'), add(range(0, parameters('rdshNumberOfInstances'))[copyIndex()], parameters('vmInitialNumber')))]", "adminUsername": "[variables('vmAdministratorUsername')]", diff --git a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customimagevm.bicep b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customimagevm.bicep index 8b6378b1a33..315afcc9d00 100644 --- a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customimagevm.bicep +++ b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customimagevm.bicep @@ -164,7 +164,7 @@ resource nic 'Microsoft.Network/networkInterfaces@2018-11-01' = [for i in range( } ] enableAcceleratedNetworking: enableAcceleratedNetworking - networkSecurityGroup: (empty(networkSecurityGroupId) ? json('null') : json('{"id": "${nsgId}"}')) + networkSecurityGroup: (empty(networkSecurityGroupId) ? null : json('{"id": "${nsgId}"}')) } dependsOn: [ NSG @@ -182,7 +182,7 @@ resource vm 'Microsoft.Compute/virtualMachines@2018-10-01' = [for i in range(0, hardwareProfile: { vmSize: rdshVmSize } - availabilitySet: ((availabilityOption == 'AvailabilitySet') ? vmAvailabilitySetResourceId : json('null')) + availabilitySet: ((availabilityOption == 'AvailabilitySet') ? vmAvailabilitySetResourceId : null) osProfile: { computerName: concat(rdshPrefix, (i + vmInitialNumber)) adminUsername: vmAdministratorUsername diff --git a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customimagevm.json b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customimagevm.json index 2d4ee113851..34325fd9ba1 100644 --- a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customimagevm.json +++ b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customimagevm.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "13456880223628676085" + "templateHash": "11204922523232167930" } }, "parameters": { @@ -309,7 +309,7 @@ } ], "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), json('null'), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" + "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), null(), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" }, "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'NSG-linkedTemplate')]" @@ -332,7 +332,7 @@ "hardwareProfile": { "vmSize": "[parameters('rdshVmSize')]" }, - "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), json('null'))]", + "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), null())]", "osProfile": { "computerName": "[concat(parameters('rdshPrefix'), add(range(0, parameters('rdshNumberOfInstances'))[copyIndex()], parameters('vmInitialNumber')))]", "adminUsername": "[variables('vmAdministratorUsername')]", diff --git a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customvhdvm.bicep b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customvhdvm.bicep index 1c68a237ccf..39fbdf05d80 100644 --- a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customvhdvm.bicep +++ b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customvhdvm.bicep @@ -181,7 +181,7 @@ resource nic 'Microsoft.Network/networkInterfaces@2018-11-01' = [for i in range( } ] enableAcceleratedNetworking: enableAcceleratedNetworking - networkSecurityGroup: (empty(networkSecurityGroupId) ? json('null') : json('{"id": "${nsgId}"}')) + networkSecurityGroup: (empty(networkSecurityGroupId) ? null : json('{"id": "${nsgId}"}')) } dependsOn: [ NSG @@ -199,7 +199,7 @@ resource vm 'Microsoft.Compute/virtualMachines@2018-10-01' = [for i in range(0, hardwareProfile: { vmSize: rdshVmSize } - availabilitySet: ((availabilityOption == 'AvailabilitySet') ? vmAvailabilitySetResourceId : json('null')) + availabilitySet: ((availabilityOption == 'AvailabilitySet') ? vmAvailabilitySetResourceId : null) osProfile: { computerName: concat(rdshPrefix, (i + vmInitialNumber)) adminUsername: vmAdministratorUsername diff --git a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customvhdvm.json b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customvhdvm.json index 5512d45598a..aefa79a251a 100644 --- a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customvhdvm.json +++ b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-customvhdvm.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "16380997742108041127" + "templateHash": "12159192052625478905" } }, "parameters": { @@ -327,7 +327,7 @@ } ], "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), json('null'), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" + "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), null(), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" }, "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'NSG-linkedTemplate')]" @@ -350,7 +350,7 @@ "hardwareProfile": { "vmSize": "[parameters('rdshVmSize')]" }, - "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), json('null'))]", + "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), null())]", "osProfile": { "computerName": "[concat(parameters('rdshPrefix'), add(range(0, parameters('rdshNumberOfInstances'))[copyIndex()], parameters('vmInitialNumber')))]", "adminUsername": "[variables('vmAdministratorUsername')]", diff --git a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-galleryvm.bicep b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-galleryvm.bicep index 5cbc25f5287..4f8a8de8112 100644 --- a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-galleryvm.bicep +++ b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-galleryvm.bicep @@ -164,7 +164,7 @@ resource nic 'Microsoft.Network/networkInterfaces@2018-11-01' = [for i in range( } ] enableAcceleratedNetworking: enableAcceleratedNetworking - networkSecurityGroup: (empty(networkSecurityGroupId) ? json('null') : json('{"id": "${nsgId}"}')) + networkSecurityGroup: (empty(networkSecurityGroupId) ? null : json('{"id": "${nsgId}"}')) } dependsOn: [ NSG @@ -182,7 +182,7 @@ resource vm 'Microsoft.Compute/virtualMachines@2018-10-01' = [for i in range(0, hardwareProfile: { vmSize: rdshVmSize } - availabilitySet: ((availabilityOption == 'AvailabilitySet') ? vmAvailabilitySetResourceId : json('null')) + availabilitySet: ((availabilityOption == 'AvailabilitySet') ? vmAvailabilitySetResourceId : null) osProfile: { computerName: concat(rdshPrefix, (i + vmInitialNumber)) adminUsername: vmAdministratorUsername diff --git a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-galleryvm.json b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-galleryvm.json index e75a18723fb..3eb4c187b9c 100644 --- a/docs/examples/201/wvd-create-hostpool/modules/managedDisks-galleryvm.json +++ b/docs/examples/201/wvd-create-hostpool/modules/managedDisks-galleryvm.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "3774652722909324444" + "templateHash": "1641105856964782992" } }, "parameters": { @@ -309,7 +309,7 @@ } ], "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), json('null'), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" + "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), null(), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" }, "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'NSG-linkedTemplate')]" @@ -332,7 +332,7 @@ "hardwareProfile": { "vmSize": "[parameters('rdshVmSize')]" }, - "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), json('null'))]", + "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), null())]", "osProfile": { "computerName": "[concat(parameters('rdshPrefix'), add(range(0, parameters('rdshNumberOfInstances'))[copyIndex()], parameters('vmInitialNumber')))]", "adminUsername": "[variables('vmAdministratorUsername')]", diff --git a/docs/examples/201/wvd-create-hostpool/modules/unmanagedDisks-customvhdvm.bicep b/docs/examples/201/wvd-create-hostpool/modules/unmanagedDisks-customvhdvm.bicep index f6643b37c6d..60eebb1ce69 100644 --- a/docs/examples/201/wvd-create-hostpool/modules/unmanagedDisks-customvhdvm.bicep +++ b/docs/examples/201/wvd-create-hostpool/modules/unmanagedDisks-customvhdvm.bicep @@ -165,7 +165,7 @@ resource nic 'Microsoft.Network/networkInterfaces@2018-11-01' = [for i in range( } ] enableAcceleratedNetworking: enableAcceleratedNetworking - networkSecurityGroup: (empty(networkSecurityGroupId) ? json('null') : json('{"id": "${nsgId}"}')) + networkSecurityGroup: (empty(networkSecurityGroupId) ? null : json('{"id": "${nsgId}"}')) } dependsOn: [ NSG @@ -183,7 +183,7 @@ resource vm 'Microsoft.Compute/virtualMachines@2018-10-01' = [for i in range(0, hardwareProfile: { vmSize: rdshVmSize } - availabilitySet: ((availabilityOption == 'AvailabilitySet') ? vmAvailabilitySetResourceId : json('null')) + availabilitySet: ((availabilityOption == 'AvailabilitySet') ? vmAvailabilitySetResourceId : null) osProfile: { computerName: concat(rdshPrefix, (i + vmInitialNumber)) adminUsername: vmAdministratorUsername diff --git a/docs/examples/201/wvd-create-hostpool/modules/unmanagedDisks-customvhdvm.json b/docs/examples/201/wvd-create-hostpool/modules/unmanagedDisks-customvhdvm.json index 81af1462889..92128400046 100644 --- a/docs/examples/201/wvd-create-hostpool/modules/unmanagedDisks-customvhdvm.json +++ b/docs/examples/201/wvd-create-hostpool/modules/unmanagedDisks-customvhdvm.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "3622114365539263083" + "templateHash": "3610555063404175184" } }, "parameters": { @@ -310,7 +310,7 @@ } ], "enableAcceleratedNetworking": "[parameters('enableAcceleratedNetworking')]", - "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), json('null'), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" + "networkSecurityGroup": "[if(empty(parameters('networkSecurityGroupId')), null(), json(format('{{\"id\": \"{0}\"}}', variables('nsgId'))))]" }, "dependsOn": [ "[resourceId('Microsoft.Resources/deployments', 'NSG-linkedTemplate')]" @@ -333,7 +333,7 @@ "hardwareProfile": { "vmSize": "[parameters('rdshVmSize')]" }, - "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), json('null'))]", + "availabilitySet": "[if(equals(parameters('availabilityOption'), 'AvailabilitySet'), variables('vmAvailabilitySetResourceId'), null())]", "osProfile": { "computerName": "[concat(parameters('rdshPrefix'), add(range(0, parameters('rdshNumberOfInstances'))[copyIndex()], parameters('vmInitialNumber')))]", "adminUsername": "[variables('vmAdministratorUsername')]", diff --git a/docs/examples/301/deployment-script-dev-environment/containergroups.bicep b/docs/examples/301/deployment-script-dev-environment/containergroups.bicep index 8e9f25cc71b..73f106fd506 100644 --- a/docs/examples/301/deployment-script-dev-environment/containergroups.bicep +++ b/docs/examples/301/deployment-script-dev-environment/containergroups.bicep @@ -47,7 +47,7 @@ resource containerGroupName 'Microsoft.ContainerInstance/containerGroups@2019-12 { name: '${name}cg' properties: { - image: type == 'AzureCLI' ? azcliImage : type == 'AzurePowerShell' ? azpwshImage : json('null') + image: type == 'AzureCLI' ? azcliImage : type == 'AzurePowerShell' ? azpwshImage : '' resources: { requests: { cpu: 1 @@ -66,7 +66,7 @@ resource containerGroupName 'Microsoft.ContainerInstance/containerGroups@2019-12 mountPath: mountPath } ] - command: type == 'AzureCLI' ? azcliCommand : type == 'AzurePowerShell' ? azpwshCommand : json('null') + command: type == 'AzureCLI' ? azcliCommand : type == 'AzurePowerShell' ? azpwshCommand : null } } ] diff --git a/docs/examples/301/deployment-script-dev-environment/containergroups.json b/docs/examples/301/deployment-script-dev-environment/containergroups.json index 08727ae64ed..583b8336cbe 100644 --- a/docs/examples/301/deployment-script-dev-environment/containergroups.json +++ b/docs/examples/301/deployment-script-dev-environment/containergroups.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "10962051538706201810" + "templateHash": "6673808017610235563" } }, "parameters": { @@ -85,7 +85,7 @@ { "name": "[format('{0}cg', parameters('name'))]", "properties": { - "image": "[if(equals(parameters('type'), 'AzureCLI'), variables('azcliImage'), if(equals(parameters('type'), 'AzurePowerShell'), variables('azpwshImage'), json('null')))]", + "image": "[if(equals(parameters('type'), 'AzureCLI'), variables('azcliImage'), if(equals(parameters('type'), 'AzurePowerShell'), variables('azpwshImage'), ''))]", "resources": { "requests": { "cpu": 1, @@ -104,7 +104,7 @@ "mountPath": "[parameters('mountPath')]" } ], - "command": "[if(equals(parameters('type'), 'AzureCLI'), variables('azcliCommand'), if(equals(parameters('type'), 'AzurePowerShell'), variables('azpwshCommand'), json('null')))]" + "command": "[if(equals(parameters('type'), 'AzureCLI'), variables('azcliCommand'), if(equals(parameters('type'), 'AzurePowerShell'), variables('azpwshCommand'), null()))]" } } ], diff --git a/docs/examples/301/deployment-script-dev-environment/main.json b/docs/examples/301/deployment-script-dev-environment/main.json index fce294814d9..6a7438094ec 100644 --- a/docs/examples/301/deployment-script-dev-environment/main.json +++ b/docs/examples/301/deployment-script-dev-environment/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "12138990045889318701" + "templateHash": "289854984096426809" } }, "parameters": { @@ -204,7 +204,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "10962051538706201810" + "templateHash": "6673808017610235563" } }, "parameters": { @@ -284,7 +284,7 @@ { "name": "[format('{0}cg', parameters('name'))]", "properties": { - "image": "[if(equals(parameters('type'), 'AzureCLI'), variables('azcliImage'), if(equals(parameters('type'), 'AzurePowerShell'), variables('azpwshImage'), json('null')))]", + "image": "[if(equals(parameters('type'), 'AzureCLI'), variables('azcliImage'), if(equals(parameters('type'), 'AzurePowerShell'), variables('azpwshImage'), ''))]", "resources": { "requests": { "cpu": 1, @@ -303,7 +303,7 @@ "mountPath": "[parameters('mountPath')]" } ], - "command": "[if(equals(parameters('type'), 'AzureCLI'), variables('azcliCommand'), if(equals(parameters('type'), 'AzurePowerShell'), variables('azpwshCommand'), json('null')))]" + "command": "[if(equals(parameters('type'), 'AzureCLI'), variables('azcliCommand'), if(equals(parameters('type'), 'AzurePowerShell'), variables('azpwshCommand'), null()))]" } } ], diff --git a/docs/examples/301/modules-vwan-to-vnet-s2s-with-fw/azfw.bicep b/docs/examples/301/modules-vwan-to-vnet-s2s-with-fw/azfw.bicep index c1d2862f14b..69fb6210df1 100644 --- a/docs/examples/301/modules-vwan-to-vnet-s2s-with-fw/azfw.bicep +++ b/docs/examples/301/modules-vwan-to-vnet-s2s-with-fw/azfw.bicep @@ -71,5 +71,5 @@ var vnetfwproperties = { resource firewall 'Microsoft.Network/azureFirewalls@2020-06-01' = { name: fwname location: location - properties: fwtype == 'VNet' ? vnetfwproperties.properties : fwtype == 'vWAN' ? hubfwproperties.properties : any(null) + properties: fwtype == 'VNet' ? vnetfwproperties.properties : fwtype == 'vWAN' ? hubfwproperties.properties : null } diff --git a/src/Bicep.Core.IntegrationTests/TypeSystem/TypeValidationTests.cs b/src/Bicep.Core.IntegrationTests/TypeSystem/TypeValidationTests.cs index eba09b9e6f2..9588936d393 100644 --- a/src/Bicep.Core.IntegrationTests/TypeSystem/TypeValidationTests.cs +++ b/src/Bicep.Core.IntegrationTests/TypeSystem/TypeValidationTests.cs @@ -1,11 +1,14 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections.Generic; +using System.Linq; +using Bicep.Core.Configuration; using Bicep.Core.Diagnostics; using Bicep.Core.Semantics; using Bicep.Core.TypeSystem; using Bicep.Core.UnitTests; using Bicep.Core.UnitTests.Assertions; +using Bicep.Core.UnitTests.Configuration; using Bicep.Core.UnitTests.Utils; using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -343,5 +346,70 @@ public void Type_validation_narrowing_on_discriminated_object_types(TypeSymbolVa ); } } + + [TestMethod] + public void Json_function_can_obtain_types_for_string_literal_json_args() + { + var program = @" +var intJson = json('123') +var floatJson = json('1234.1224') +var stringJson = json('""hello!""') +var nullJson = json('null') +var jsonWithComments = json(''' +{ + //here's a comment! + ""key"": ""value"" /* multi-line + comment */ +} +''') + +var objectJson = json('{""validProp"": ""validValue""}') +var propAccess = objectJson.validProp +var commentsPropAccess = jsonWithComments.key +var invalidPropAccess = objectJson.invalidProp +"; + + var model = GetSemanticModelForTest(program, Enumerable.Empty()); + + GetTypeForNamedSymbol(model, "objectJson").Name.Should().Be("object"); + GetTypeForNamedSymbol(model, "propAccess").Name.Should().Be("'validValue'"); + + GetTypeForNamedSymbol(model, "intJson").Name.Should().Be("int"); + GetTypeForNamedSymbol(model, "floatJson").Name.Should().Be("any"); + GetTypeForNamedSymbol(model, "stringJson").Name.Should().Be("'hello!'"); + GetTypeForNamedSymbol(model, "nullJson").Name.Should().Be("null"); + GetTypeForNamedSymbol(model, "commentsPropAccess").Name.Should().Be("'value'"); + + GetTypeForNamedSymbol(model, "invalidPropAccess").Name.Should().Be("error"); + + var noLinterConfig = new ConfigHelper().GetDisabledLinterConfig(); + model.GetAllDiagnostics(noLinterConfig).Should().SatisfyRespectively( + x => x.Should().HaveCodeAndSeverity("BCP083", DiagnosticLevel.Error).And.HaveMessage("The type \"object\" does not contain property \"invalidProp\". Did you mean \"validProp\"?") + ); + } + + [TestMethod] + public void Json_function_returns_error_for_unparseable_json() + { + var program = @" +var invalidJson = json('{""prop"": ""value') +"; + + var model = GetSemanticModelForTest(program, Enumerable.Empty()); + + GetTypeForNamedSymbol(model, "invalidJson").Name.Should().Be("error"); + + var noLinterConfig = new ConfigHelper().GetDisabledLinterConfig(); + model.GetAllDiagnostics(noLinterConfig).Should().SatisfyRespectively( + x => x.Should().HaveCodeAndSeverity("BCP186", DiagnosticLevel.Error).And.HaveMessage("Unable to parse literal JSON value. Please ensure that it is well-formed.") + ); + } + + private static TypeSymbol GetTypeForNamedSymbol(SemanticModel model, string symbolName) + { + var symbol = model.Root.GetDeclarationsByName(symbolName).Single(); + + return symbol.Type; + } } } diff --git a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/Assets/test.json.txt b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/Assets/test.json.txt new file mode 100644 index 00000000000..18399acbdf6 --- /dev/null +++ b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/Assets/test.json.txt @@ -0,0 +1,11 @@ +{ + "string": "someVal", + "int": 123, + "array": [ + 1, + 2 + ], + "object": { + "nestedString": "someVal" + } +} \ No newline at end of file diff --git a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.bicep b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.bicep index 7da386e6dad..210e8641674 100644 --- a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.bicep +++ b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.bicep @@ -67,3 +67,10 @@ var loadWithEncoding07 = loadTextContent('Assets/encoding-ascii.txt', 'iso-8859- var loadWithEncoding08 = loadTextContent('Assets/encoding-ascii.txt', 'utf-8') var loadWithEncoding11 = loadTextContent('Assets/encoding-utf8.txt', 'utf-8') var loadWithEncoding12 = loadTextContent('Assets/encoding-utf8-bom.txt', 'utf-8') + +var testJson = json(loadTextContent('./Assets/test.json.txt')) +var testJsonString = testJson.string +var testJsonInt = testJson.int +var testJsonArrayVal = testJson.array[0] +var testJsonObject = testJson.object +var testJsonNestedString = testJson.object.nestedString diff --git a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.diagnostics.bicep b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.diagnostics.bicep index 34bfcaff381..b7bd71d0aa0 100644 --- a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.diagnostics.bicep +++ b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.diagnostics.bicep @@ -93,3 +93,15 @@ var loadWithEncoding11 = loadTextContent('Assets/encoding-utf8.txt', 'utf-8') var loadWithEncoding12 = loadTextContent('Assets/encoding-utf8-bom.txt', 'utf-8') //@[4:22) [no-unused-vars (Warning)] Variable "loadWithEncoding12" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-vars)) |loadWithEncoding12| +var testJson = json(loadTextContent('./Assets/test.json.txt')) +var testJsonString = testJson.string +//@[4:18) [no-unused-vars (Warning)] Variable "testJsonString" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-vars)) |testJsonString| +var testJsonInt = testJson.int +//@[4:15) [no-unused-vars (Warning)] Variable "testJsonInt" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-vars)) |testJsonInt| +var testJsonArrayVal = testJson.array[0] +//@[4:20) [no-unused-vars (Warning)] Variable "testJsonArrayVal" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-vars)) |testJsonArrayVal| +var testJsonObject = testJson.object +//@[4:18) [no-unused-vars (Warning)] Variable "testJsonObject" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-vars)) |testJsonObject| +var testJsonNestedString = testJson.object.nestedString +//@[4:24) [no-unused-vars (Warning)] Variable "testJsonNestedString" is declared but never used. (CodeDescription: bicep core(https://aka.ms/bicep/linter/no-unused-vars)) |testJsonNestedString| + diff --git a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.formatted.bicep b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.formatted.bicep index 568147f2861..2fa1a465384 100644 --- a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.formatted.bicep +++ b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.formatted.bicep @@ -66,3 +66,10 @@ var loadWithEncoding07 = loadTextContent('Assets/encoding-ascii.txt', 'iso-8859- var loadWithEncoding08 = loadTextContent('Assets/encoding-ascii.txt', 'utf-8') var loadWithEncoding11 = loadTextContent('Assets/encoding-utf8.txt', 'utf-8') var loadWithEncoding12 = loadTextContent('Assets/encoding-utf8-bom.txt', 'utf-8') + +var testJson = json(loadTextContent('./Assets/test.json.txt')) +var testJsonString = testJson.string +var testJsonInt = testJson.int +var testJsonArrayVal = testJson.array[0] +var testJsonObject = testJson.object +var testJsonNestedString = testJson.object.nestedString diff --git a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.json b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.json index 1e680bee4cd..a8c8551626e 100644 --- a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.json +++ b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "dev", - "templateHash": "14178017593798113874" + "templateHash": "6380745596059945961" } }, "functions": [], @@ -55,7 +55,13 @@ "loadWithEncoding07": "32 = \n33 = !\n34 = \"\n35 = #\n36 = $\n37 = %\n38 = &\n39 = '\n40 = (\n41 = )\n42 = *\n43 = +\n44 = ,\n45 = -\n46 = .\n47 = /\n48 = 0\n49 = 1\n50 = 2\n51 = 3\n52 = 4\n53 = 5\n54 = 6\n55 = 7\n56 = 8\n57 = 9\n58 = :\n59 = ;\n60 = <\n61 = =\n62 = >\n63 = ?\n64 = @\n65 = A\n66 = B\n67 = C\n68 = D\n69 = E\n70 = F\n71 = G\n72 = H\n73 = I\n74 = J\n75 = K\n76 = L\n77 = M\n78 = N\n79 = O\n80 = P\n81 = Q\n82 = R\n83 = S\n84 = T\n85 = U\n86 = V\n87 = W\n88 = X\n89 = Y\n90 = Z\n91 = [\n92 = \\\n93 = ]\n94 = ^\n95 = _\n96 = `\n97 = a\n98 = b\n99 = c\n100 = d\n101 = e\n102 = f\n103 = g\n104 = h\n105 = i\n106 = j\n107 = k\n108 = l\n109 = m\n110 = n\n111 = o\n112 = p\n113 = q\n114 = r\n115 = s\n116 = t\n117 = u\n118 = v\n119 = w\n120 = x\n121 = y\n122 = z\n123 = {\n124 = |\n125 = }\n126 = ~\n", "loadWithEncoding08": "32 = \n33 = !\n34 = \"\n35 = #\n36 = $\n37 = %\n38 = &\n39 = '\n40 = (\n41 = )\n42 = *\n43 = +\n44 = ,\n45 = -\n46 = .\n47 = /\n48 = 0\n49 = 1\n50 = 2\n51 = 3\n52 = 4\n53 = 5\n54 = 6\n55 = 7\n56 = 8\n57 = 9\n58 = :\n59 = ;\n60 = <\n61 = =\n62 = >\n63 = ?\n64 = @\n65 = A\n66 = B\n67 = C\n68 = D\n69 = E\n70 = F\n71 = G\n72 = H\n73 = I\n74 = J\n75 = K\n76 = L\n77 = M\n78 = N\n79 = O\n80 = P\n81 = Q\n82 = R\n83 = S\n84 = T\n85 = U\n86 = V\n87 = W\n88 = X\n89 = Y\n90 = Z\n91 = [\n92 = \\\n93 = ]\n94 = ^\n95 = _\n96 = `\n97 = a\n98 = b\n99 = c\n100 = d\n101 = e\n102 = f\n103 = g\n104 = h\n105 = i\n106 = j\n107 = k\n108 = l\n109 = m\n110 = n\n111 = o\n112 = p\n113 = q\n114 = r\n115 = s\n116 = t\n117 = u\n118 = v\n119 = w\n120 = x\n121 = y\n122 = z\n123 = {\n124 = |\n125 = }\n126 = ~\n", "loadWithEncoding11": "💪😊😈🍕☕\r\n🐱‍👤\r\n\r\n朝辞白帝彩云间\r\n千里江陵一日还\r\n两岸猿声啼不住\r\n轻舟已过万重山\r\n\r\nΠ π Φ φ\r\n\r\n😎\r\n\r\nαα\r\nΩω\r\nΘ \r\n\r\nZażółć gęślą jaźń\r\n\r\náéóúñü - ¡Hola!\r\n\r\n二头肌二头肌\r\n\r\n\r\n二头肌\r\nΘ二头肌α\r\n\r\n𐐷\r\n\\u{10437}\r\n\\u{D801}\\u{DC37}\r\n\r\n❆ Hello\\u{20}World\\u{21} ❁\r\n\r\n\ta\tb\tc\td\te\tf\tg\th\t\r\n8\t♜\t♞\t♝\t♛\t♚\t♝\t♞\t♜\t8\r\n7\t♟\t♟\t♟\t♟\t♟\t♟\t♟\t♟\t7\r\n6\t\t\t\t\t\t\t\t\t6\r\n5\t\t\t\t\t\t\t\t\t5\r\n4\t\t\t\t\t\t\t\t\t4\r\n3\t\t\t\t\t\t\t\t\t3\r\n2\t♙\t♙\t♙\t♙\t♙\t♙\t♙\t♙\t2\r\n1\t♖\t♘\t♗\t♕\t♔\t♗\t♘\t♖\t1\r\n\ta\tb\tc\td\te\tf\tg\th\r\n", - "loadWithEncoding12": "💪😊😈🍕☕\r\n🐱‍👤\r\n\r\n朝辞白帝彩云间\r\n千里江陵一日还\r\n两岸猿声啼不住\r\n轻舟已过万重山\r\n\r\nΠ π Φ φ\r\n\r\n😎\r\n\r\nαα\r\nΩω\r\nΘ \r\n\r\nZażółć gęślą jaźń\r\n\r\náéóúñü - ¡Hola!\r\n\r\n二头肌二头肌\r\n\r\n\r\n二头肌\r\nΘ二头肌α\r\n\r\n𐐷\r\n\\u{10437}\r\n\\u{D801}\\u{DC37}\r\n\r\n❆ Hello\\u{20}World\\u{21} ❁\r\n\r\n\ta\tb\tc\td\te\tf\tg\th\t\r\n8\t♜\t♞\t♝\t♛\t♚\t♝\t♞\t♜\t8\r\n7\t♟\t♟\t♟\t♟\t♟\t♟\t♟\t♟\t7\r\n6\t\t\t\t\t\t\t\t\t6\r\n5\t\t\t\t\t\t\t\t\t5\r\n4\t\t\t\t\t\t\t\t\t4\r\n3\t\t\t\t\t\t\t\t\t3\r\n2\t♙\t♙\t♙\t♙\t♙\t♙\t♙\t♙\t2\r\n1\t♖\t♘\t♗\t♕\t♔\t♗\t♘\t♖\t1\r\n\ta\tb\tc\td\te\tf\tg\th\r\n" + "loadWithEncoding12": "💪😊😈🍕☕\r\n🐱‍👤\r\n\r\n朝辞白帝彩云间\r\n千里江陵一日还\r\n两岸猿声啼不住\r\n轻舟已过万重山\r\n\r\nΠ π Φ φ\r\n\r\n😎\r\n\r\nαα\r\nΩω\r\nΘ \r\n\r\nZażółć gęślą jaźń\r\n\r\náéóúñü - ¡Hola!\r\n\r\n二头肌二头肌\r\n\r\n\r\n二头肌\r\nΘ二头肌α\r\n\r\n𐐷\r\n\\u{10437}\r\n\\u{D801}\\u{DC37}\r\n\r\n❆ Hello\\u{20}World\\u{21} ❁\r\n\r\n\ta\tb\tc\td\te\tf\tg\th\t\r\n8\t♜\t♞\t♝\t♛\t♚\t♝\t♞\t♜\t8\r\n7\t♟\t♟\t♟\t♟\t♟\t♟\t♟\t♟\t7\r\n6\t\t\t\t\t\t\t\t\t6\r\n5\t\t\t\t\t\t\t\t\t5\r\n4\t\t\t\t\t\t\t\t\t4\r\n3\t\t\t\t\t\t\t\t\t3\r\n2\t♙\t♙\t♙\t♙\t♙\t♙\t♙\t♙\t2\r\n1\t♖\t♘\t♗\t♕\t♔\t♗\t♘\t♖\t1\r\n\ta\tb\tc\td\te\tf\tg\th\r\n", + "testJson": "[json('{\n \"string\": \"someVal\",\n \"int\": 123,\n \"array\": [\n 1,\n 2\n ],\n \"object\": {\n \"nestedString\": \"someVal\"\n }\n}')]", + "testJsonString": "[variables('testJson').string]", + "testJsonInt": "[variables('testJson').int]", + "testJsonArrayVal": "[variables('testJson').array[0]]", + "testJsonObject": "[variables('testJson').object]", + "testJsonNestedString": "[variables('testJson').object.nestedString]" }, "resources": [ { diff --git a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.symbols.bicep b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.symbols.bicep index 9d19f711a85..b8a0f58413b 100644 --- a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.symbols.bicep +++ b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.symbols.bicep @@ -95,3 +95,16 @@ var loadWithEncoding11 = loadTextContent('Assets/encoding-utf8.txt', 'utf-8') var loadWithEncoding12 = loadTextContent('Assets/encoding-utf8-bom.txt', 'utf-8') //@[4:22) Variable loadWithEncoding12. Type: '💪😊😈🍕☕\r\n🐱‍👤\r\n\r\n朝辞白帝彩云间\r\n千里江陵一日还\r\n两岸猿声啼不住\r\n轻舟已过万重山\r\n\r\nΠ π Φ φ\r\n\r\n😎\r\n\r\nαα\r\nΩω\r\nΘ \r\n\r\nZażółć gęślą jaźń\r\n\r\náéóúñü - ¡Hola!\r\n\r\n二头肌二头肌\r\n\r\n\r\n二头肌\r\nΘ二头肌α\r\n\r\n𐐷\r\n\\u{10437}\r\n\\u{D801}\\u{DC37}\r\n\r\n❆ Hello\\u{20}World\\u{21} ❁\r\n\r\n\ta\tb\tc\td\te\tf\tg\th\t\r\n8\t♜\t♞\t♝\t♛\t♚\t♝\t♞\t♜\t8\r\n7\t♟\t♟\t♟\t♟\t♟\t♟\t♟\t♟\t7\r\n6\t\t\t\t\t\t\t\t\t6\r\n5\t\t\t\t\t\t\t\t\t5\r\n4\t\t\t\t\t\t\t\t\t4\r\n3\t\t\t\t\t\t\t\t\t3\r\n2\t♙\t♙\t♙\t♙\t♙\t♙\t♙\t♙\t2\r\n1\t♖\t♘\t♗\t♕\t♔\t♗\t♘\t♖\t1\r\n\ta\tb\tc\td\te\tf\tg\th\r\n'. Declaration start char: 0, length: 81 +var testJson = json(loadTextContent('./Assets/test.json.txt')) +//@[4:12) Variable testJson. Type: object. Declaration start char: 0, length: 62 +var testJsonString = testJson.string +//@[4:18) Variable testJsonString. Type: 'someVal'. Declaration start char: 0, length: 36 +var testJsonInt = testJson.int +//@[4:15) Variable testJsonInt. Type: int. Declaration start char: 0, length: 30 +var testJsonArrayVal = testJson.array[0] +//@[4:20) Variable testJsonArrayVal. Type: int. Declaration start char: 0, length: 40 +var testJsonObject = testJson.object +//@[4:18) Variable testJsonObject. Type: object. Declaration start char: 0, length: 36 +var testJsonNestedString = testJson.object.nestedString +//@[4:24) Variable testJsonNestedString. Type: 'someVal'. Declaration start char: 0, length: 55 + diff --git a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.syntax.bicep b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.syntax.bicep index 12f84c8acaf..c1ac8921903 100644 --- a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.syntax.bicep +++ b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.syntax.bicep @@ -701,6 +701,107 @@ var loadWithEncoding12 = loadTextContent('Assets/encoding-utf8-bom.txt', 'utf-8' //@[73:80) StringSyntax //@[73:80) StringComplete |'utf-8'| //@[80:81) RightParen |)| -//@[81:83) NewLine |\r\n| +//@[81:85) NewLine |\r\n\r\n| + +var testJson = json(loadTextContent('./Assets/test.json.txt')) +//@[0:62) VariableDeclarationSyntax +//@[0:3) Identifier |var| +//@[4:12) IdentifierSyntax +//@[4:12) Identifier |testJson| +//@[13:14) Assignment |=| +//@[15:62) FunctionCallSyntax +//@[15:19) IdentifierSyntax +//@[15:19) Identifier |json| +//@[19:20) LeftParen |(| +//@[20:61) FunctionArgumentSyntax +//@[20:61) FunctionCallSyntax +//@[20:35) IdentifierSyntax +//@[20:35) Identifier |loadTextContent| +//@[35:36) LeftParen |(| +//@[36:60) FunctionArgumentSyntax +//@[36:60) StringSyntax +//@[36:60) StringComplete |'./Assets/test.json.txt'| +//@[60:61) RightParen |)| +//@[61:62) RightParen |)| +//@[62:64) NewLine |\r\n| +var testJsonString = testJson.string +//@[0:36) VariableDeclarationSyntax +//@[0:3) Identifier |var| +//@[4:18) IdentifierSyntax +//@[4:18) Identifier |testJsonString| +//@[19:20) Assignment |=| +//@[21:36) PropertyAccessSyntax +//@[21:29) VariableAccessSyntax +//@[21:29) IdentifierSyntax +//@[21:29) Identifier |testJson| +//@[29:30) Dot |.| +//@[30:36) IdentifierSyntax +//@[30:36) Identifier |string| +//@[36:38) NewLine |\r\n| +var testJsonInt = testJson.int +//@[0:30) VariableDeclarationSyntax +//@[0:3) Identifier |var| +//@[4:15) IdentifierSyntax +//@[4:15) Identifier |testJsonInt| +//@[16:17) Assignment |=| +//@[18:30) PropertyAccessSyntax +//@[18:26) VariableAccessSyntax +//@[18:26) IdentifierSyntax +//@[18:26) Identifier |testJson| +//@[26:27) Dot |.| +//@[27:30) IdentifierSyntax +//@[27:30) Identifier |int| +//@[30:32) NewLine |\r\n| +var testJsonArrayVal = testJson.array[0] +//@[0:40) VariableDeclarationSyntax +//@[0:3) Identifier |var| +//@[4:20) IdentifierSyntax +//@[4:20) Identifier |testJsonArrayVal| +//@[21:22) Assignment |=| +//@[23:40) ArrayAccessSyntax +//@[23:37) PropertyAccessSyntax +//@[23:31) VariableAccessSyntax +//@[23:31) IdentifierSyntax +//@[23:31) Identifier |testJson| +//@[31:32) Dot |.| +//@[32:37) IdentifierSyntax +//@[32:37) Identifier |array| +//@[37:38) LeftSquare |[| +//@[38:39) IntegerLiteralSyntax +//@[38:39) Integer |0| +//@[39:40) RightSquare |]| +//@[40:42) NewLine |\r\n| +var testJsonObject = testJson.object +//@[0:36) VariableDeclarationSyntax +//@[0:3) Identifier |var| +//@[4:18) IdentifierSyntax +//@[4:18) Identifier |testJsonObject| +//@[19:20) Assignment |=| +//@[21:36) PropertyAccessSyntax +//@[21:29) VariableAccessSyntax +//@[21:29) IdentifierSyntax +//@[21:29) Identifier |testJson| +//@[29:30) Dot |.| +//@[30:36) IdentifierSyntax +//@[30:36) Identifier |object| +//@[36:38) NewLine |\r\n| +var testJsonNestedString = testJson.object.nestedString +//@[0:55) VariableDeclarationSyntax +//@[0:3) Identifier |var| +//@[4:24) IdentifierSyntax +//@[4:24) Identifier |testJsonNestedString| +//@[25:26) Assignment |=| +//@[27:55) PropertyAccessSyntax +//@[27:42) PropertyAccessSyntax +//@[27:35) VariableAccessSyntax +//@[27:35) IdentifierSyntax +//@[27:35) Identifier |testJson| +//@[35:36) Dot |.| +//@[36:42) IdentifierSyntax +//@[36:42) Identifier |object| +//@[42:43) Dot |.| +//@[43:55) IdentifierSyntax +//@[43:55) Identifier |nestedString| +//@[55:57) NewLine |\r\n| //@[0:0) EndOfFile || diff --git a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.tokens.bicep b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.tokens.bicep index 80227a8ea32..0e21ff961f2 100644 --- a/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.tokens.bicep +++ b/src/Bicep.Core.Samples/Files/LoadFunctions_CRLF/main.tokens.bicep @@ -429,6 +429,64 @@ var loadWithEncoding12 = loadTextContent('Assets/encoding-utf8-bom.txt', 'utf-8' //@[71:72) Comma |,| //@[73:80) StringComplete |'utf-8'| //@[80:81) RightParen |)| -//@[81:83) NewLine |\r\n| +//@[81:85) NewLine |\r\n\r\n| + +var testJson = json(loadTextContent('./Assets/test.json.txt')) +//@[0:3) Identifier |var| +//@[4:12) Identifier |testJson| +//@[13:14) Assignment |=| +//@[15:19) Identifier |json| +//@[19:20) LeftParen |(| +//@[20:35) Identifier |loadTextContent| +//@[35:36) LeftParen |(| +//@[36:60) StringComplete |'./Assets/test.json.txt'| +//@[60:61) RightParen |)| +//@[61:62) RightParen |)| +//@[62:64) NewLine |\r\n| +var testJsonString = testJson.string +//@[0:3) Identifier |var| +//@[4:18) Identifier |testJsonString| +//@[19:20) Assignment |=| +//@[21:29) Identifier |testJson| +//@[29:30) Dot |.| +//@[30:36) Identifier |string| +//@[36:38) NewLine |\r\n| +var testJsonInt = testJson.int +//@[0:3) Identifier |var| +//@[4:15) Identifier |testJsonInt| +//@[16:17) Assignment |=| +//@[18:26) Identifier |testJson| +//@[26:27) Dot |.| +//@[27:30) Identifier |int| +//@[30:32) NewLine |\r\n| +var testJsonArrayVal = testJson.array[0] +//@[0:3) Identifier |var| +//@[4:20) Identifier |testJsonArrayVal| +//@[21:22) Assignment |=| +//@[23:31) Identifier |testJson| +//@[31:32) Dot |.| +//@[32:37) Identifier |array| +//@[37:38) LeftSquare |[| +//@[38:39) Integer |0| +//@[39:40) RightSquare |]| +//@[40:42) NewLine |\r\n| +var testJsonObject = testJson.object +//@[0:3) Identifier |var| +//@[4:18) Identifier |testJsonObject| +//@[19:20) Assignment |=| +//@[21:29) Identifier |testJson| +//@[29:30) Dot |.| +//@[30:36) Identifier |object| +//@[36:38) NewLine |\r\n| +var testJsonNestedString = testJson.object.nestedString +//@[0:3) Identifier |var| +//@[4:24) Identifier |testJsonNestedString| +//@[25:26) Assignment |=| +//@[27:35) Identifier |testJson| +//@[35:36) Dot |.| +//@[36:42) Identifier |object| +//@[42:43) Dot |.| +//@[43:55) Identifier |nestedString| +//@[55:57) NewLine |\r\n| //@[0:0) EndOfFile || diff --git a/src/Bicep.Core.UnitTests/Configuration/ConfigHelperExtensions.cs b/src/Bicep.Core.UnitTests/Configuration/ConfigHelperExtensions.cs index 2fbf016ee6e..2eca1ac0e84 100644 --- a/src/Bicep.Core.UnitTests/Configuration/ConfigHelperExtensions.cs +++ b/src/Bicep.Core.UnitTests/Configuration/ConfigHelperExtensions.cs @@ -16,6 +16,5 @@ public static class ConfigHelperExtensions public static ConfigHelper GetDisabledLinterConfig(this ConfigHelper configHelper) => configHelper.OverrideSetting(LinterAnalyzer.LinterEnabledSetting, false) .OverrideSetting(LinterAnalyzer.LinterVerboseSetting, false); - } } diff --git a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs index 752a5dcfac5..cfc911dc1b6 100644 --- a/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs +++ b/src/Bicep.Core/Diagnostics/DiagnosticBuilder.cs @@ -1089,6 +1089,11 @@ public ErrorDiagnostic RuntimeValueNotAllowedInVariableForBody(string variableNa DiagnosticLevel.Info, "BCP185", $"Encoding mismatch. File was loaded with '{detectedEncoding}' encoding."); + + public ErrorDiagnostic UnparseableJsonType() => new( + TextSpan, + "BCP186", + $"Unable to parse literal JSON value. Please ensure that it is well-formed."); } public static DiagnosticBuilderInternal ForPosition(TextSpan span) diff --git a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceSymbol.cs b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceSymbol.cs index ed8cf181854..615bb58fc0e 100644 --- a/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceSymbol.cs +++ b/src/Bicep.Core/Semantics/Namespaces/SystemNamespaceSymbol.cs @@ -5,11 +5,13 @@ using System.Collections.Immutable; using System.Linq; using System.Text; +using Azure.Deployments.Core.Json; using Bicep.Core.Diagnostics; using Bicep.Core.Extensions; using Bicep.Core.FileSystem; using Bicep.Core.Syntax; using Bicep.Core.TypeSystem; +using Newtonsoft.Json.Linq; namespace Bicep.Core.Semantics.Namespaces { @@ -369,9 +371,9 @@ public class SystemNamespaceSymbol : NamespaceSymbol .Build(), new FunctionOverloadBuilder("json") - .WithReturnType(LanguageConstants.Any) .WithDescription("Converts a valid JSON string into a JSON data type.") .WithRequiredParameter("json", LanguageConstants.String, "The value to convert to JSON. The string must be a properly formatted JSON string.") + .WithDynamicReturnType(JsonTypeBuilder, LanguageConstants.Any) .Build(), new FunctionOverloadBuilder("dateTimeAdd") @@ -504,6 +506,51 @@ private static SyntaxBase StringLiteralFunctionReturnTypeEvaluator(FunctionCallS return SyntaxFactory.CreateStringLiteral(stringLiteral.RawStringValue); } + private static TypeSymbol JsonTypeBuilder(IBinder binder, IFileResolver fileResolver, IDiagnosticWriter diagnostics, ImmutableArray arguments, ImmutableArray argumentTypes) + { + static TypeSymbol ToBicepType(JToken token) + => token switch { + JObject @object => new ObjectType( + "object", + TypeSymbolValidationFlags.Default, + @object.Properties().Select(x => new TypeProperty(x.Name, ToBicepType(x.Value), TypePropertyFlags.ReadOnly | TypePropertyFlags.ReadableAtDeployTime)), + null), + JArray @array => new TypedArrayType( + UnionType.Create(@array.Select(x => ToBicepType(x))), + TypeSymbolValidationFlags.Default), + JValue value => value.Type switch { + JTokenType.String => new StringLiteralType(value.ToString()), + JTokenType.Integer => LanguageConstants.Int, + // Floats are currently not supported in Bicep, so fall back to the default behavior of "any" + JTokenType.Float => LanguageConstants.Any, + JTokenType.Boolean => LanguageConstants.Bool, + JTokenType.Null => LanguageConstants.Null, + _ => LanguageConstants.Any, + }, + _ => LanguageConstants.Any, + }; + + if (argumentTypes.Length != 1 || argumentTypes[0] is not StringLiteralType stringLiteral) + { + return LanguageConstants.Any; + } + + // Purposefully use the same method and parsing settings as the deployment engine, + // to provide as much consistency as possible. + if (stringLiteral.RawStringValue.TryFromJson() is not {} token) + { + // Instead of catching and returning the JSON parse exception, we simply return a generic error. + // This avoids having to deal with localization, and avoids possible confusion regarding line endings in the message. + // If the in-line JSON is so complex that troubleshooting is difficult, then that's a sign that the user should + // instead break it out into a separate file and use loadTextContent(). + var error = DiagnosticBuilder.ForPosition(arguments[0].Expression).UnparseableJsonType(); + + return ErrorType.Create(error); + } + + return ToBicepType(token); + } + // TODO: Add copyIndex here when we support loops. private static readonly ImmutableArray BannedFunctions = new[] {