/src/Razor/Microsoft.NET.Sdk.Razor/src/ApplyCssScopes.cs

https://github.com/aspnet/AspNetCore · C# · 96 lines · 74 code · 12 blank · 10 comment · 5 complexity · 85ff3a29116677417dc2926a3302708c MD5 · raw file

  1. // Copyright (c) .NET Foundation. All rights reserved.
  2. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text.RegularExpressions;
  7. using Microsoft.Build.Framework;
  8. using Microsoft.Build.Utilities;
  9. namespace Microsoft.AspNetCore.Razor.Tasks
  10. {
  11. public class ApplyCssScopes : Task
  12. {
  13. [Required]
  14. public ITaskItem[] RazorComponents { get; set; }
  15. [Required]
  16. public ITaskItem[] ScopedCss { get; set; }
  17. [Output]
  18. public ITaskItem[] RazorComponentsWithScopes { get; set; }
  19. public override bool Execute()
  20. {
  21. var razorComponentsWithScopes = new List<ITaskItem>();
  22. var unmatchedScopedCss = new List<ITaskItem>(ScopedCss);
  23. var scopedCssByComponent = new Dictionary<string, IList<ITaskItem>>();
  24. for (var i = 0; i < RazorComponents.Length; i++)
  25. {
  26. var componentCandidate = RazorComponents[i];
  27. var j = 0;
  28. while (j < unmatchedScopedCss.Count)
  29. {
  30. var scopedCssCandidate = unmatchedScopedCss[j];
  31. var explicitRazorcomponent = scopedCssCandidate.GetMetadata("RazorComponent");
  32. var razorComponent = !string.IsNullOrWhiteSpace(explicitRazorcomponent) ?
  33. explicitRazorcomponent :
  34. Regex.Replace(scopedCssCandidate.ItemSpec, "(.*)\\.razor\\.css$", "$1.razor", RegexOptions.IgnoreCase);
  35. if (string.Equals(componentCandidate.ItemSpec, razorComponent, StringComparison.OrdinalIgnoreCase))
  36. {
  37. unmatchedScopedCss.RemoveAt(j);
  38. if (!scopedCssByComponent.TryGetValue(componentCandidate.ItemSpec, out var existing))
  39. {
  40. scopedCssByComponent[componentCandidate.ItemSpec] = new List<ITaskItem>() { scopedCssCandidate };
  41. var item = new TaskItem(componentCandidate);
  42. item.SetMetadata("CssScope", scopedCssCandidate.GetMetadata("CssScope"));
  43. razorComponentsWithScopes.Add(item);
  44. }
  45. else
  46. {
  47. existing.Add(scopedCssCandidate);
  48. }
  49. }
  50. else
  51. {
  52. j++;
  53. }
  54. }
  55. }
  56. foreach (var kvp in scopedCssByComponent)
  57. {
  58. var component = kvp.Key;
  59. var scopeFiles = kvp.Value;
  60. if (scopeFiles.Count > 1)
  61. {
  62. Log.LogError(null, "BLAZOR101", "", component, 0, 0, 0, 0, $"More than one scoped css files were found for the razor component '{component}'. " +
  63. $"Each razor component must have at most a single associated scoped css file." +
  64. Environment.NewLine +
  65. string.Join(Environment.NewLine, scopeFiles.Select(f => f.ItemSpec)));
  66. }
  67. }
  68. // We don't want to allow scoped css files without a matching component. Our convention is very specific in its requirements
  69. // so failing to have a matching component very likely means an error.
  70. // When the matching component was specified explicitly, failing to find a matching component is an error.
  71. // This simplifies a few things like being able to assume that the presence of a .razor.css file or a ScopedCssInput item will result in a bundle being produced,
  72. // that the contents of the bundle are independent of the existence of a component and that users will be able to catch errors at compile
  73. // time instead of wondering why their component doesn't have a scope applied to it.
  74. // In the rare case that a .razor file exists on the user project, has an associated .razor.css file and the user decides to exclude it as a RazorComponent they
  75. // can update the Content item for the .razor.css file with Scoped=false and we will not consider it.
  76. foreach (var unmatched in unmatchedScopedCss)
  77. {
  78. Log.LogError(null, "BLAZOR102", "", unmatched.ItemSpec, 0, 0, 0, 0, $"The scoped css file '{unmatched.ItemSpec}' was defined but no associated razor component was found for it.");
  79. }
  80. RazorComponentsWithScopes = razorComponentsWithScopes.ToArray();
  81. return !Log.HasLoggedErrors;
  82. }
  83. }
  84. }