Skip to content

Conversation

@axunonb
Copy link
Collaborator

@axunonb axunonb commented Mar 30, 2025

@codecov
Copy link

codecov bot commented Mar 30, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

❌ Your project status has failed because the head coverage (67%) is below the target coverage (80%). You can increase the head coverage or adjust the target coverage.

Impacted file tree graph

@@         Coverage Diff         @@
##           main   #758   +/-   ##
===================================
+ Coverage    66%    67%   +1%     
===================================
  Files       104    104           
  Lines      4592   4519   -73     
  Branches   1146   1106   -40     
===================================
- Hits       3033   3014   -19     
+ Misses     1124   1072   -52     
+ Partials    435    433    -2     
Files with missing lines Coverage Δ
Ical.Net/Constants.cs 45% <ø> (ø)
Ical.Net/DataTypes/RecurrencePattern.cs 74% <ø> (ø)
...alization/DataTypes/RecurrencePatternSerializer.cs 95% <100%> (+23%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@axunonb
Copy link
Collaborator Author

axunonb commented Mar 30, 2025

@minichma TBD:
RecurrencePatternSerializer.TryDeserializeNaturalLanguagePattern can process kind of natural language input. This ability is not in use and fully untested. Also this is part of the regular deserialization and gets called when the FREQ part is missing. I'm in doubt whether this functionality brings any added value. Should we remove it completely or at least use a different entry point for this kind of parsing?

BTW: Just noticed, we can ask Copilot for a review, too :)

@axunonb axunonb requested a review from Copilot March 30, 2025 16:10
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request enhances the RecurrencePatternSerializer by supporting RRULE parts in any order, refactoring methods to reduce cognitive complexity, and separating the deserialization logic for RFC-compliant input and natural language input.

  • Updated regex names and helper method extraction to improve parsing flexibility
  • Revised processing of key-value pairs and numeric temporal units
  • Added comprehensive test cases for validating RRULE parsing in any order

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs Refactored deserialization methods to support order-independent RRULE parts and natural language input
Ical.Net/DataTypes/RecurrencePattern.cs Added documentation for the BySetPosition property
Ical.Net.Tests/SerializationTests.cs Introduced new test cases ensuring RRULE properties are correctly deserialized regardless of order
Comments suppressed due to low confidence (1)

Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs:421

  • [nitpick] Returning false immediately in TryAddNumericTemporalUnit when parsing fails changes the original behavior (which skipped the invalid value). Consider continuing the loop instead to avoid prematurely terminating processing of comma-separated fields.
if (!int.TryParse(match.Groups["Num"].Value, out var num))

@minichma
Copy link
Collaborator

Didn't look into the actual implementation so far, but generally speaking I think the standard serializers/deserializers should stick to the RFC. So when parsing a calendar from any other format, this should happen fully intentional (not as a fallback from the standard parser) and independently. I therefore agree that it should rather not be an integral part of standard deserialization.

If this included as part of Ical.Net, it should be implemented in separate classes that could be used independently. The functionality reminds me of #576. Could this be combined?

@axunonb axunonb force-pushed the axunonb/wip/pr/rec-pat-serializer branch 2 times, most recently from 5717fd6 to 191d839 Compare March 30, 2025 18:19
- RRULE parts are now processed in any order, which is compliant to RFC5545 section 3.3.10.
- Refactor methods with high cognitive complexity
- Split deserialization of RFC-compliant and natural language input
@axunonb axunonb force-pushed the axunonb/wip/pr/rec-pat-serializer branch from 191d839 to 1000abc Compare March 30, 2025 18:20
@axunonb
Copy link
Collaborator Author

axunonb commented Mar 30, 2025

The functionality reminds me of #576. Could this be combined?

Yes, it has some similarities. I wonder whether natural langue for recurrences brings much advantage. End users would be better off with a good GUI, while devops might prefer a builder with a fluent API.

Anyway, the natural language part is undocumented, untested, and can never be all fallback for RFC-compliance. I'll remove it.

@axunonb axunonb requested a review from Copilot March 30, 2025 18:51
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR enhances the RecurrencePatternSerializer to process RRULE parts in any order per RFC5545, reduces cognitive complexity by refactoring methods, and removes natural language input deserialization.

  • Refactored deserialization logic using a dedicated FrequencyPattern regex and helper methods
  • Removed legacy regex patterns and natural language parsing logic
  • Updated tests to verify order-independent RRULE deserialization and recurrence range checks

Reviewed Changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs Refactored deserialization logic and removed natural language parsing regex definitions
Ical.Net/DataTypes/RecurrencePattern.cs Added XML documentation for the BySetPosition property
Ical.Net.Tests/RecurrenceTests.cs Added tests to validate order independence and recurrence range checks
Comments suppressed due to low confidence (2)

Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs:179

  • [nitpick] The constant 'CiCompiled' is now using PascalCase, which may be inconsistent with the previous naming style for constants. Consider adopting a consistent naming convention (such as all uppercase or a consistent prefix) for constants across the codebase.
private const RegexOptions CiCompiled = RegexOptions.IgnoreCase | RegexOptions.Compiled;

Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs:321

  • [nitpick] Consider trimming whitespace from the 'day' string in the AddWeekDays method to prevent potential issues when parsing day names.
byDay.Add(new WeekDay(day));


private void ProcessKeyValuePair(string key, string value, RecurrencePattern r, ISerializerFactory factory)
{
switch (key)
Copy link

Copilot AI Mar 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The switch statement in ProcessKeyValuePair does not include a default case to handle unexpected keys. Adding a default branch (even if only for logging or graceful handling) would improve robustness.

Copilot uses AI. Check for mistakes.
@axunonb axunonb marked this pull request as ready for review March 30, 2025 18:54
@axunonb axunonb requested a review from minichma March 30, 2025 18:54
Copy link
Collaborator

@minichma minichma left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, much better! Just a few minor comment.


private const RegexOptions CiCompiled = RegexOptions.IgnoreCase | RegexOptions.Compiled;

internal static readonly Regex FrequencyPattern =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make private?


internal static readonly Regex FrequencyPattern =
new Regex(@"FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY);?(.*)", CiCompiled,
RegexDefaults.Timeout);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know, this is legacy, but I think, we shouldn't specify a timeout in Regex. Timeouts in Regex are good if the pattern is user-specified, but in our case there is no reason to expect any bad behavior. On the other hand, the timeout could easily cause unexpected behavior. E.g. on a server running close to its limits, a timeout of 200 ms could easily be hit also on well-formed input.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's make the timeout 2000 ms, which is unlikely ever to be hit


private const RegexOptions CiCompiled = RegexOptions.IgnoreCase | RegexOptions.Compiled;

internal static readonly Regex FrequencyPattern =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need this regex at all (anymore)? The only thing it really does now is validating that a RRULE contains a FREQ=xxx string (e.g. thisIsNotFREQ=DAILY! would work), but that's really not too helpful as either all parts of the string would have to be validated or nothing. The real validation happens throughout the parsing process.

{
// Parse the frequency type
r.Frequency = (FrequencyType) Enum.Parse(typeof(FrequencyType), match.Groups[1].Value, true);
return TryDeserializeFrequencyPattern(value, r, factory)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the pattern can't be parsed, why would we return null? Shouldn't we raise an exception?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will change to always throw on failure

}
else

CheckMutuallyExclusive("COUNT", "UNTIL", r.Count, r.Until);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check frequency is present here, rather than doing the regex upfront.

if (keywordPair.Length == 0)
{
r.Interval = 1;
// ignore subsequent semi-colons
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.. as well as preceding and duplicate ones

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all kinds - will add a unit test

break;

case "bysecond":
AddInt32Values(r.BySecond, value);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By adding the entry, we accept BYSECOND to appear more than once (same for all 'BY' parts). Legacy though.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

won't check

// Couldn't parse the object, return null!
r = null;
case "freq":
r.Frequency = (FrequencyType) Enum.Parse(typeof(FrequencyType), value, true);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess we should fail if None is specified.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point

case "wkst":
r.FirstDayOfWeek = GetDayOfWeek(value);
break;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we add a default case for unknown/invalid rule parts and fail here?

minichma
minichma previously approved these changes Mar 30, 2025
@axunonb
Copy link
Collaborator Author

axunonb commented Mar 31, 2025

Agree with your comments on existing code. Starting with a bug fix and ending up with a rewrite, while limited time is running by...

@sonarqubecloud
Copy link

@axunonb axunonb requested review from Copilot and minichma March 31, 2025 12:46
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR enhances the RecurrencePatternSerializer by refactoring its deserialization logic to process RRULE parts in any order in accordance with RFC5545 and by removing natural language deserialization.

  • Refactored methods to reduce cognitive complexity and improve maintainability.
  • Removed unused regex patterns related to natural language inputs.
  • Updated regex timeout constant and added new tests for various RRULE orderings and error cases.

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
Ical.Net/Serialization/DataTypes/RecurrencePatternSerializer.cs Refactored deserialization logic, removed natural language regexes, and added helper methods.
Ical.Net/DataTypes/RecurrencePattern.cs Added XML documentation for the BySetPosition property.
Ical.Net/Constants.cs Increased the regex timeout from 200ms to 2000ms.
Ical.Net.Tests/RecurrenceTests.cs Added tests validating RRULE property order, missing FREQ handling, and unsupported parts.
Comments suppressed due to low confidence (1)

Ical.Net/Constants.cs:345

  • Increasing the timeout from 200ms to 2000ms may have performance implications in parts of the system that rely on time-bound regex operations. Confirm that this change is acceptable for all affected components.
public const int TimeoutMilliseconds = 2000;

Comment on lines +201 to 203
System.Diagnostics.Debug.Assert(r != null);
System.Diagnostics.Debug.Assert(factory != null);

Copy link

Copilot AI Mar 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Using Debug.Assert for null checks may not enforce the condition in Release builds. Consider using an explicit exception or a proper null-check to ensure robust error handling in production.

Suggested change
System.Diagnostics.Debug.Assert(r != null);
System.Diagnostics.Debug.Assert(factory != null);
if (r == null)
{
throw new InvalidOperationException("Failed to create RecurrencePattern.");
}
if (factory == null)
{
throw new InvalidOperationException("Serializer factory service is not available.");
}

Copilot uses AI. Check for mistakes.
@axunonb axunonb merged commit 65dc5ef into main Mar 31, 2025
7 of 8 checks passed
@axunonb axunonb deleted the axunonb/wip/pr/rec-pat-serializer branch March 31, 2025 12:53
@axunonb axunonb restored the axunonb/wip/pr/rec-pat-serializer branch March 31, 2025 12:53
@axunonb axunonb deleted the axunonb/wip/pr/rec-pat-serializer branch March 31, 2025 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RecurrencePatternSerializer expects that the FREQ` is the first in Recurrence Rules

3 participants