Skip to content

Simplify the PlantUML import patterns - #521

Draft
alexanderlinne wants to merge 2 commits into
mainfrom
refactor-plantuml-import-patterns
Draft

alexanderlinne wants to merge 2 commits into
mainfrom
refactor-plantuml-import-patterns

Conversation

@alexanderlinne

Copy link
Copy Markdown
Collaborator

Why

The next three PRs rewrite the component and dependency patterns, so this PR first makes them readable and pins what they match without changing it. PlantUmlPatterns was hard to read and to change:

  • The component regex was assembled from five static readonly string fields, some set in a static constructor, through Capture and AnythingBut helpers, and anchored twice (\A(?:^ … )\z). To see what it matched, you had to put it together in your head.
  • Stereotypes were read by a second regex over the whole line, instead of from the stereotype section of the component match. MatchStereoTypes() stepped a stored Match forward, so a second call returned an empty set.
  • Every component line was matched twice, once by FilterComponents and again by the PlantUmlComponentMatcher constructor, which threw if the line didn't match.
  • PlantUmlPatterns was an object without state that the parser created with new, and the "matchers" were really parsed results, read through Match…() methods.

What changes

Tests first. The first commit adds tests through PlantUmlParser only, so that they survive the refactoring:

  • the error paths: Parse(null) for a file and a stream, a missing file, and a stream whose Read throws,
  • characterization tests for every optional part of the patterns: stereotypes with and without whitespace between them, quoted and unquoted aliases used with and without brackets, surrounding whitespace, and lines that must not count as components,
  • dependencies in both directions, with and without brackets and a : description, and lines that must not count as dependencies.

All of them pass on main.

Patterns. Each regex is one readable definition. PlantUmlPatterns becomes a static class that holds the shared stereotype fragment, a named and repeated stereotype group read through Captures.

Matchers. PlantUmlComponentMatcher.TryParse returns null for lines that aren't components, and PlantUmlDependencyMatcher.TryParse replaces TryParseFromLeftToRight and TryParseFromRightToLeft. Each line is matched once, and Name, Alias, Stereotypes, Origin and Target are properties. FilterComponents, MatchComponent, the unreachable throw and the unreachable null checks go away. The matchers stay in PlantUmlPatterns.cs, so that the PRs on top rebase cleanly.

Parser.

  • It calls the TryParse methods directly, without the PlantUmlPatterns instance.
  • The two ReadLines overloads, which repeated the same catch … when filter, become one.
  • A redundant Distinct() before a HashSet is dropped.
  • Comment lines are filtered without building a regex for every line.

Main's way of splitting dependency lines stays as it is; #PR2 replaces it.

After this PR, PlantUmlPatterns.cs and PlantUmlParser.cs have 100% line and branch coverage.

Behaviour change

Because stereotypes are now read from the component match, text that only looks like a stereotype is no longer read as one. Before, [A] <<Real.*>> as "<<Fake.*>>" gave A the stereotypes Real.* and Fake.*, and [A] as "<<Foo>>" was accepted although it has no stereotype. A regression test pins this. Apart from that, the import matches exactly what it matched before.

Cover the error paths of PlantUmlParser: null arguments, a missing file
and a stream that fails while reading.

Pin the optional parts of the component and dependency patterns with
characterization tests, so they can be rewritten safely: stereotypes
with and without whitespace between them, quoted and unquoted aliases,
surrounding whitespace, lines that are not components, and dependencies
in both directions with and without brackets and descriptions.

Signed-off-by: Alexander Linne <alexander.linne@tngtech.com>
Write each regex as one readable definition instead of assembling it
from fields in a static constructor via Capture and AnythingBut.
PlantUmlPatterns becomes a static class holding the shared stereotype
fragment, and the matchers become parsed results: TryParse returns null
(or nothing) for lines that don't match, so each component line is
matched once, and the Match...() methods become properties. This removes
FilterComponents, MatchComponent and the unreachable throw and null
checks.

In the parser, merge the two ReadLines overloads, drop a redundant
Distinct() and stop building a regex for every line to filter comments.

Stereotypes are now read from a named group of the component match
instead of a second pass over the whole line. Before, a stereotype-like
alias was read as a stereotype too, so `[A] <<Real.*>> as "<<Fake.*>>"`
gave A the stereotypes Real.* and Fake.*, and `[A] as "<<Foo>>"` was
accepted although it has no stereotype.

Signed-off-by: Alexander Linne <alexander.linne@tngtech.com>
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.72%. Comparing base (f439e0d) to head (edf1e95).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #521      +/-   ##
==========================================
+ Coverage   86.55%   86.72%   +0.17%     
==========================================
  Files         261      261              
  Lines       12648    12559      -89     
  Branches     1236     1231       -5     
==========================================
- Hits        10947    10892      -55     
+ Misses       1359     1330      -29     
+ Partials      342      337       -5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

This branch has not been deployed

No deployments
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.

2 participants