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
42 changes: 21 additions & 21 deletions source/timewarp-source-generators/file-name-rule-analyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public class FileNameRuleAnalyzer : IIncrementalGenerator
{
public const string DiagnosticId = "TWA001";
private const string Category = "Naming";

private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId,
"File name should use kebab-case",
Expand All @@ -15,10 +15,10 @@ public class FileNameRuleAnalyzer : IIncrementalGenerator
isEnabledByDefault: false,
description: "C# file names should use kebab-case format with hyphens separating words, all lowercase."
);

// Regex pattern for valid kebab-case file names
private static readonly Regex KebabCasePattern = new(@"^[a-z][a-z0-9]*(?:-[a-z0-9]+)*\.cs$", RegexOptions.Compiled);

// Default exception patterns
private static readonly string[] DefaultExceptions =
[
Expand All @@ -38,7 +38,7 @@ public class FileNameRuleAnalyzer : IIncrementalGenerator
"AnalyzerReleases.Shipped.md",
"AnalyzerReleases.Unshipped.md"
];

public void Initialize(IncrementalGeneratorInitializationContext context)
{
// Create a value provider that provides all syntax trees with config options
Expand All @@ -49,72 +49,72 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
(Compilation compilation, AnalyzerConfigOptionsProvider configOptions) = source;
return compilation.SyntaxTrees.Select(tree => (tree, configOptions));
});

// Register diagnostics for each syntax tree
context.RegisterSourceOutput(syntaxTreesWithConfig, (spc, source) =>
{
(SyntaxTree tree, AnalyzerConfigOptionsProvider configOptions) = source;
AnalyzeFileNaming(spc, tree, configOptions);
});
}

private void AnalyzeFileNaming(SourceProductionContext context, SyntaxTree tree, AnalyzerConfigOptionsProvider configOptions)
{
string filePath = tree.FilePath;

// Skip if file path is empty or null
if (string.IsNullOrEmpty(filePath))
return;

string fileName = Path.GetFileName(filePath);

// Skip if not a C# file
if (!fileName.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
return;

// Get configured exceptions
string[] exceptions = GetConfiguredExceptions(configOptions, tree);

// Check if file matches any exception pattern
if (IsFileExcepted(fileName, exceptions))
return;

// Check if file name follows kebab-case pattern
if (!KebabCasePattern.IsMatch(fileName))
{
var location = Location.Create(
tree,
TextSpan.FromBounds(0, 0)
);

var diagnostic = Diagnostic.Create(Rule, location, fileName);
context.ReportDiagnostic(diagnostic);
}
}

private string[] GetConfiguredExceptions(AnalyzerConfigOptionsProvider configOptions, SyntaxTree tree)
{
// Get file-specific options
AnalyzerConfigOptions options = configOptions.GetOptions(tree);

// Try to get configured exceptions from .editorconfig
if (options.TryGetValue(
"dotnet_diagnostic.TWA001.excluded_files",
"dotnet_diagnostic.TWA001.excluded_files",
out string? configuredExceptions) && !string.IsNullOrEmpty(configuredExceptions))
{
// Split by semicolon and trim whitespace
IEnumerable<string> additionalExceptions = configuredExceptions
.Split([';'], StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.Trim());

// Merge defaults with configured exceptions
return [.. DefaultExceptions, .. additionalExceptions];
}

// Return default exceptions if not configured
return DefaultExceptions;
}

private bool IsFileExcepted(string fileName, string[] exceptions)
{
foreach (string exception in exceptions)
Expand All @@ -125,7 +125,7 @@ private bool IsFileExcepted(string fileName, string[] exceptions)
string pattern = exception
.Replace(".", "\\.")
.Replace("*", ".*");

if (Regex.IsMatch(fileName, $"^{pattern}$", RegexOptions.IgnoreCase))
return true;
}
Expand All @@ -136,7 +136,7 @@ private bool IsFileExcepted(string fileName, string[] exceptions)
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,20 @@ namespace TimeWarp.SourceGenerators;
/// public partial class DataService : ILogger, IDataProcessor&lt;string&gt;
/// {
/// [Implements]
/// private readonly ILogger _logger;
/// private readonly ILogger Logger;
///
/// [Implements]
/// private readonly IDataProcessor&lt;string&gt; _processor;
/// private readonly IDataProcessor&lt;string&gt; Processor;
///
/// public DataService(ILogger logger, IDataProcessor&lt;string&gt; processor)
/// {
/// _logger = logger;
/// _processor = processor;
/// Logger = logger;
/// Processor = processor;
/// }
/// }
/// </code>
/// The generator will create forwarding implementations for all ILogger methods/properties to _logger
/// and all IDataProcessor&lt;string&gt; methods/properties to _processor.
/// The generator will create forwarding implementations for all ILogger methods/properties to Logger
/// and all IDataProcessor&lt;string&gt; methods/properties to Processor.
/// </example>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public class ImplementsAttribute : Attribute
{
}
public class ImplementsAttribute : Attribute { }
Loading