Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Roslyn source generator #200

Open
wants to merge 52 commits into
base: main
Choose a base branch
from
Open

Conversation

Code-Grump
Copy link
Contributor

@Code-Grump Code-Grump commented Jul 3, 2024

🤔 What's changed?

Adds new Roslyn-based source generator to convert feature files into executable tests.

⚡️ What's your motivation?

🏷️ What kind of change is this?

  • ⚡ New feature (non-breaking change which adds new behaviour)

♻️ Anything particular you want feedback on?

  • The code structures for the modelling of features and scenarios
  • The models for test fixtures and methods
  • The mechanisms for rendering source code
  • The tests to validate surface-level code generation

📋 Checklist:

  • I've changed the behaviour of the code
    • I have added/updated tests to cover my changes.
  • My change requires a change to the documentation.
    • I have updated the documentation accordingly.
  • Users should know about my change
    • I have added an entry to the "[vNext]" section of the CHANGELOG, linking to this pull request & included my GitHub handle to the release contributors list.

Tracking review notes:

  • Review note 1: opt-in behavior
  • Review note 2: Delete *.feature.cs files
  • Review note 3: how to configure / editorconfig
  • Review note 4: test execution does not utilize incremental gen, always builds (accepted limitation)
  • Review note 5: performance degradation of incremental command line build (accepted limitation)
  • Review note 6: Namespace of generated classes in feature files in sub folders
  • Review note 7: rename project and nuget package
  • Review note 8: nullable warnings
  • Review note 9: <PrivateAssets> and <IncludeAssets>
  • Review note 10: breakpoint highlight column alignment issue
  • Review note 11: navigate to feature file from test explorer
  • Review note 12: Debugger.Launch()
  • Review note 13: scenario changes are not immediately reflected in the generated code
  • Review note 14: test discovery does not utilize incremental gen (accepted limitation)
  • Review note 15: wrong source info for errors generated for syntax errors
  • Review note 16: shifted line/column for errors generated for syntax errors during build
  • Review note 17: duplicated errors after build (accepted limitation)
  • Review note 18: missing new lines in generated code of scenario outline
  • Review note 19: Compilation error when scenario outlines used for MsTest v3
  • Review note 20: DataTables and DocString parameters are not supported.
  • Review note 21: The scenario description is not passed to the ScenarioInfo.
  • Review note 22: Disable MsBuild gen variables (e.g. ReqnrollFeatureFiles) when Roslyn gen is active
  • Review note 23: CSharpSyntax.CreateIdentifier special handling
  • Review note 24: Scenario outline parameter substitutions for special cases
  • Review note 25: Scenario outline special example headers
  • Review note 26: Scenario outline empty example headers
  • Review note 27: Scenario outline duplicate example headers
  • Review note 28: Support allowRowTests=false
  • Review note 29: support generator/addNonParallelizableMarkerForTags
  • Review note 30: Support backgrounds
  • Review note 31: shared extended gherkin parser / additional validations
  • Review note 32: emitIgnoredExamples config

This text was originally taken from the template of the Cucumber project, then edited by hand. You can modify the template here.

@Code-Grump
Copy link
Contributor Author

Code-Grump commented Jul 3, 2024

My current focus is to get all test scenarios in the System tests to pass for MSTest, but I'm not able to decipher the test-failure for scenario. The output is both massive and cryptic, and I would appreciate help translating the failure into something actionable.

But I'm also slightly thwarted by the build here failing for the Analyser package as it's not your standard NuGet package.

@obligaron
Copy link
Contributor

Do you mean the Handles_different_scenario_and_scenario_outline_outcomes test?

This one fails, because the logic in Roslyn Source Generator is missing a special case for ignored examples.

In the old generator the special logic is in MsTestV2GeneratorProvider:

// MsTest doesn't support to ignore a specific test case / DataRow
if (isIgnored)
{
    return;
}

If I add a check for the ignore tag to the GenerateTestMethodAttributes method in the roslyn source generator, the tests runs fine:

var moreData = values.Skip(1).ToList();

if (set.Tags.Any(x => x.Equals("ignore", StringComparison.InvariantCultureIgnoreCase)))
    continue;

moreData.Add(set.Tags);

Hope that helps. 🙂

@Code-Grump
Copy link
Contributor Author

Ignored examples don't get executed, so I dropped in a little config switch to enable emitting / omitting code for these examples. I made the default to omit examples with an ignore tag and all the MSTest examples pass now. Next up: fixing the NuGet packaging failure in the build pipeline.

@Code-Grump
Copy link
Contributor Author

The error NU5017 is quite specific, relating to a lack of assemblies or assembly references in a package: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu5017

This is true for Roslyn packages and I don't see this error building locally, even when running an identical dotnet build command as seen in the CI pipeline. I am clueless and in need of further help.

@obligaron
Copy link
Contributor

The CI sets two environment variables to true, if you do the same you can reproduce it locally:

set REQNROLL_TEST_PIPELINEMODE=true
set GITHUB_ACTIONS=true

Likely the second one is causing the problem, as the following is configured in Directory.Build.props:

  <PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
    <GitBranch Condition=" '$(GitBranch)' == ''">$(GITHUB_REF_NAME)</GitBranch>
    <GitCommitSha Condition=" '$(GitCommitSha)' == ''">$(GITHUB_SHA)</GitCommitSha>
    <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  </PropertyGroup>

This also creates a snupkg and this snupkg fails to generate. Unfortunately, the error message is not very good. There is also an issue to improve the error message in the NuGet Repro (NuGet/Home#10372).

A quick fix would be to disable the inclusion of the pdb (and also the snupkg) in the FeatureTestGenerator.csproj:

    <IncludeSymbols>false</IncludeSymbols>

@Code-Grump Code-Grump marked this pull request as ready for review July 12, 2024 18:25
@Code-Grump
Copy link
Contributor Author

@gasparnagy We're finally here: a generator that produces C# code for the three supported test frameworks. Would you give me your impression of the code-changes and thoughts on what's next? I think we could certainly do with more testing, but it's also tempting to put it in the hands of users to get real feedback.

@Code-Grump Code-Grump self-assigned this Jul 12, 2024
@gasparnagy
Copy link
Contributor

I did an initial review, but I provide my notes as a comment, not as a regular review, because it will be easier to track it this way. Most of my comments are not related to a particular code anyway. I numbered the notes, so that we can better track them.

General feedback

After reviewing the code, I am convinced that the idea and the approach is good and this will be a cool improvement in Reqnroll. So 🙏!

The code in general is clean and was understandable for me. Quite a big change, so extra kudos for taking the time.

Rollout

  1. The feature is currently opt-in, so it does not change the existing behavior as long as it is not enabled. I like the approach you have taken to enable it by adding the extra NuGet package Reqnroll.FeatureSourceGenerator. I have also verified how safe it is (e.g. if the Reqnroll.FeatureSourceGenerator package is before/after/between other Reqnroll packages, but it worked. Later we can easily change this to opt-out, by including the Reqnroll.FeatureSourceGenerator package in the Reqnroll.MsTest/NUnit/xUnit packages instead of the Reqnroll.Tools.MsBuild.Generation (and ask ppl to add Reqnroll.Tools.MsBuild.Generation if they wish to use the old model). We have to prepare that this two models will co-exists for quite some time (because of the lack of generator plugin support mainly). But this is good.

  2. I have tried different upgrade scenarios and they generally worked. The only issue is with the deletion of the old *.feature.cs files. My suggestion would be to include a build target in Reqnroll.FeatureSourceGenerator that delete all *.feature.cs files in the source tree. I know it is a bit of an overhead once the feature is in use, but probably not significant and it would make the usage much-more convenient. (I have also struggled with the deletion and deleting them from a complex folder tree requires scripting knowledge.)

  3. This is a more generic one. We should review once more the configuration question. The code currently allows to be configured through MsBuild properties and .editorconfig settings, but all of our configuration so far was in the reqnroll.json. Ideally we should get the config from the reqnroll.json only. This has many benefits (well-known, easy to handle with other tools, we can provide intellisense via schema, coherent with runtime config, easier documentation, etc.), so first of all I would like to discuss what issues can we imagine with that.

Performance review

I did some performance measures with a project that has 200 scenarios (in 20 files) and with a project that has 5000 scenarios (in 500 files). The performance of the roslyn gen is pretty good, provides a 30-50% improvement over the old one.

Building a Reqnroll project with 5000 scenarios took ~10 seconds on my 10 year old dev machine, which is pretty good.

Here are the full numbers if you are interested: https://docs.google.com/spreadsheets/d/1dvvKnG6EYdvOgsoVnyMN_6Q5jwkNAmE-/edit?usp=sharing&ouid=113727882162066279604&rtpof=true&sd=true

There are some interesting learnings though:

  1. Unfortunately VS test execution cannot currently utilize the incremental gen - when you run a test it makes a full build that regenerates all (nothing we can do about it, I guess).

  2. The old gen provided better performance if nothing changed and you did a build from command line (because it somehow detected that the code behind files are up-to-date), the roslyn one now regenerates all also in this case. But this is only for the command line, from VS it seems that we anyway regenerated everything all the time, so the performance improvement is there. It is a pity that the roslyn incremental gen cannot utilize some persistent cache to declare things up-to-date. Again, I think this is good enough for now.

Behavior feedback

As the code was pretty clean, most of my feedback is rather about the behavior and not the code itself. I have found a few typos in the code, but I commit a fix for them. But once the behavior is finalized I will do another round of code review.

For the feedback I did a lot of experimentation with MsTest, but did not try NUnit and xUnit in detail. So it might be that some of my comments are generic, some are MsTest specific.

Some of my comments are marked with "TODO" prefix. These are not bugs, but just things that are not yet implemented with the new gen. I thought I will list these, to have a comprehensive task list.

You will see that many of the comments are about strange cases that normally never happen. So they seem to be nitpicking, but in this scale they do happen, so we have to ensure that the gen works for them.

The comments are in a kind of random order, I could not really group them better.

  1. The namespace of the generated classes does not seem to include the folders. My feature files were in a Features folder, so I expected the namespace to be ReqnrollCalculator.Specs.Features (like with the old one), but got ReqnrollCalculator.Specs. This is important, because the feature class names might not be globally unique and also it is better to see the tests in a hierarchy in the test explorer.

  2. I was puzzling about this, but I think I would rename the Reqnroll.FeatureSourceGenerator project and package to something like Reqnroll.Tools.Roslyn.Generation, to match the Reqnroll.Tools.MsBuild.Generation package name. Not a nice name, but at least it would be in sync with the existing one. This package will anyway be hidden from the users once we switch to opt-out model. What do you think?

  3. Maybe is is just my setup, but for me the generated files contain nullable warnings, like xxx\ReqnrollCalculator.Specs\Features\Addition.feature(39,55): warning CS8600: Converting null literal or possible null value to non-nullable type. [xxx\ReqnrollCalculator.Specs\ReqnrollCalculator.Specs.csproj]. I have seen in the code that you did a lot of magic to support nullable, but I wonder whether this is needed at all. This is generated code anyway, and the detail nullable rules and warnings seem to be changing in every .NET version. So maybe we should just simply ignore all nullable warnings for the whole generated file with a pragma.

  4. I have noticed that if you add the Reqnroll.FeatureSourceGenerator package through VS, it automatically lists the <PrivateAssets> and <IncludeAssets> in the project file. I also tried what happens if I delete these but it still worked. Do we need these? What would be the case if the Reqnroll.FeatureSourceGenerator will not directly listed by the projects, but through the Reqnroll.MsTest package?

  5. If you add a breakpoint to the feature file and debug the test, when the breakpoint is hit, it looks strange in VS (see below). This is a known problem: somehow VS draws the red box based on the indentation of the generated code. To avoid that, in the old gen, we applied special indentation to the generated code to match the indentation of the feature file (see the usage of SourceLineScope in ScenarioPartHelper.GenerateStep). I know that makes the generated code more ugly, but I am not aware of any better solution currently.

image

  1. If you double click on a test in a test explorer, it should navigate to the related scenario in the feature file. With the new gen it does not. At the source it seems to display the file name of the generated file, instead of the feature file. Probably this is because we miss an additional line pragma around the method name. We need to check the old code behind and apply the same to the new one.

image

  1. The code has a Debugger.Launch() in TestFixtureSourceGenerator (that I had to comment out), but it made me thinking if we would like to support some special MsBuild variable, like ReqnrollDebugGenerator and if this is set we perform the Debugger.Launch(). That way you can better debug a concrete sample without rebuilding Reqnroll.

  2. I don't know if this is a bug or a feature (it does not seem to impact anything), but I observed the following: When I open the generated code and the feature file side-by-side and I change the feature name, the generated code instantly changes (even before saving the file -- cool), however, if I change for example a scenario name, the change is not visible. I have to make a dummy change in the feature name to see the updated code. See the sceencapture. As I mentioned it does not seem to change the behavior (see next comment though), but for debugging is it annoying because you might see an outdated generated file. But maybe we can't do anything with this.

  3. It seems that unfortunately currently we cannot really use the benefits of incremental generators, because although the generation is done and the test class is updated, the "test explorer" window does not support refreshing based on generated files, so you only see the change once you build the project. (The test explorer can detect changes in .cs file and update the list without waiting for build, but it seems this only works for physical files.) I guess if we would need to use the generated classes from other c# code then the early generation would be useful, but since only the test explorer uses these classes... Anyway. Again probably we cannot do too much with this (maybe submit an issue to MS once the feature is live), but hoping for MS to implement this. Sad.

  4. If the feature file has an error (e.g. if you change the Then keyword somewhere to xThen), the error from the roslyn gen nicely comes in a few seconds and visible in the error dialog. The source information is wrong though, if you double click on it, it navigates to the first line of the .csproj file.

  5. If you have an error, like in 15), and you build, you get a separate error in the error list. Clicking on that navigates to the feature file, but the positions are shifted: it navigates to the line below the error and also to a character too right. This is the good old zero vs one based indexing problem of file positions. You need to do a -1 for the line and col somewhere.

  6. I'm not sure we can do anything with this, but if you have an error and build, you see every error twice: one that comes from the VS source gen and one that you get from the build... Have you heard of any solution for this? Not critical though.

  7. In the generated code, I noticed that for scenario outlines, where you build up the arguments, there are no new lines, but things come to a single line: argumentsOfScenario.Add("first", first);argumentsOfScenario.Add("second", second);var ruleTags = new string[] { };

  8. MsTest. You will like this. The approach you use for "[DataRow]" only works with MsTest v2.*. With MsTest v3, the attribute signature changed and contains a single "params" array and the generated code fails to run. To stay cross-compatible we had to use a special trick, see MsTestV2GeneratorProvider.SetRow. I know that is ugly, but I could not find any better. So for now I would just apply the same hack with a note. We can revert that once we stop supporting MsTest v2 (but unfortunately that is still quite commonly used).

  9. TODO: DataTables and DocString parameters are not supported yet. I think these should be one of the next steps, because they are commonly used.

  10. TODO: The scenario description (the free text after the scenario line and before the first step line) is not passed to the ScenarioInfo currently.

  11. The way you disable the MsTest gen when the new is in place is great. I was just wondering if we could do that even more. A typical problem with the MsTest gen is the following: if you want to start a feature file based on a similar one and you duplicate the file in VS (with Ctrl+C,V or Ctrl+dragging in solution explorer), it makes unnecessary changes in the project file (see below). This is because the MsBuild gen builds up some collections and somehow VS thinks it needs to update them. It would be great if we could also switch that off when the new gen is enabled, so at least that issue would vanish. This is just a nice-to-have though.

  <ItemGroup>
    <Gherkin Remove="Features\Multiplication - Copy.feature" />
  </ItemGroup>
  ...
  <ItemGroup>
    <ReqnrollFeatureFiles Update="Features\Multiplication - Copy.feature">
      <Visible>$(UsingMicrosoftNETSdk)</Visible>
      <CodeBehindFile>%(RelativeDir)%(Filename).feature$(DefaultLanguageSourceExtension)</CodeBehindFile>
    </ReqnrollFeatureFiles>
  </ItemGroup>
  1. I generally like the CSharpSyntax.CreateIdentifier implementation, but I noticed that it is not compatible with the old CodeFormattingExtensions.ToIdentifier that we have used so far. This is not necessarily a problem, but I remember that in a hacky plugin I had to re-calculate the same names to be able to call the tests through reflection. So such a code would break with the new identifier generation. We will need to state this. I also remember that we were struggling a lot with non-latin, accented and number characters. Maybe it was the old .NET issues, but we would need to make a deep testing for this which I haven't done now. (Unfortunately CodeFormattingExtensions.ToIdentifier does not have tests, but we can guess some cases based on the code of that.)

  2. The GenerateStepInvocations.CSharpTestFixtureGenerator handles scenario outline parameter substitutions by replacing them with {0}, {1}, etc so that you can use a string.Format. The code in the current form does not seem to handle the cases though when the step text itself contains {0}. The old one replaced { and } with {{ and }} in the text, but this is not so easy here, because later in CSharpTestMethod.RenderScenarioStepInvocationTo you decide whether you use string.Format based on whether the arguments are empty (so otherwise you would need to undo the {{ and }} stuff). So what you can do is after you collected the arguments, if you find that they are empty you initialize the StepInvocation with the original text. (In general it is not that good to use the StepInvocation.Text both as a literal text and also as a text template, so I would consider creating a separate TextTemplate or TextFormat field additionally.)

  3. Special Scenario Outline handling issue: I have taken a test scenario outline from the TestGeneratorTest class (see below). It causes an arg exception (warning CS8785: Generator 'CSharpTestFixtureSourceGenerator' failed to generate source. It will not contribute to the output and compilation errors may occur as a result. Exception was of type 'ArgumentException' with message 'Value cannot be an empty identifier.) and hence no test is generated at all. I think it boils down to the cases that we had to handle in the old gen UnitTestMethodGenerator.CreateParamToIdentifierMapping: the example header values might be empty or become empty once to try to identifyerize them (e.g. contain only punctuation) and they are also not guaranteed to be unique. See also the related 31)

		Scenario Outline: Simple Scenario Outline that's got single quotes
		                Given there is something
		                When I do <what>
		                Then something should happen with <i> <%> <i, %>
		            Examples: 
		                | what           | %  | i  | i, %|
		                | something      | 11 |.23 | 23  |
		                | something else | 22 |.56 | 56  |
  1. Similar like above: empty example header. This is obviously wrong, so it is also good if we provide a proper error message.
		Scenario Outline: Simple Scenario Outline that's got single quotes
		                Given there is something
		                When I do <what>
		            Examples: 
		                |            | 
		                | something      | 
		                | something else | 

  1. Similar like above: duplicate examples header. This is obviously wrong, so it is also good if we provide a proper error message.
		Scenario Outline: Simple Scenario Outline that's got single quotes
		                Given there is something
		                When I do <what>
		            Examples: 
		                | what           | what           |
				| something      | something      |
				| something else | something else |
  1. TODO: In the old gen, there was an option (generator/allowRowTests=false) to generate individual methods for scenario outline instead of one data-driven test method. This was unfortunately required because in some circumstances the test executors are not properly supporting data-driven tests. This is not a priority feature, but we should keep it in mind.

  2. TODO: also there was a feature to mark scenarios with a tag that you don't want to run parallel, through the generator/addNonParallelizableMarkerForTags config setting. Again, not a priority, but we should keep this on in mind too.

  3. TODO: The Background section is currently not supported (you can have backgrounds for the feature file and also for the rule). See UnitTestMethodGenerator.GenerateTestMethodBody for a reference implementation. This is also more priority.

  4. The current code uses the Gherkin parser directly and not the ReqnrollGherkinParser which is our inherited one. I guess the motivation behind this was to reduce the dependencies. Or? The ReqnrollGherkinParser does not add much to the original parser, but there are some semantic validations that are done in it that are not done in the base Gherkin parser (currently there are four: DuplicateScenariosValidator, DuplicateExamplesValidator, MissingExamplesValidator and DuplicateExamplesColumnHeadersValidator). These are things that should be checked by the new gen as well anyway, so I think in general it would be useful to use this extended parser instead, to avoid duplicated handling of these checks. I remember you have mentioned some constraints about references, but I don't remember the exact details. Do you have any suggestion for this?

  5. I wonder why the emitIgnoredExamples should be configurable (it was not so far). Do you have any specific case of that?

@ajeckmans
Copy link
Contributor

For point 10, can't you do this by also generating #line pragmas with column ranges?

@Code-Grump
Copy link
Contributor Author

Regarding 32: it's because there's a discrepancy between our current implementations: MSTest and xUnit emit these examples, where the nUnit handling omits them. They're excluded early in the pipeline, too late for the framework handler to get involved. The easiest solution was to make it a toggle and have the NUnit package switch it off.

@Code-Grump
Copy link
Contributor Author

Regarding 3: configuration using an additional file can be done. We'd have to include it in the compilation to have Roslyn be able to access its contents.

The major downside of this would be that you'd not be able to influence generation settings on a per file/folder basis as you can with .editorconfig.

My personal preference is to migrate users to . editorconfig settings, following the convention of all Roslyn components. It's a new generator with new rules!

@Code-Grump
Copy link
Contributor Author

Regarding 9: Those directives are to stop the analyzer being included in the compiled output of the project. Essentially, you only want it as a design/build-time dependency and not as a runtime component. It's a standard practice for Roslyn components to be included with these restrictions to avoid them transitively finding their way into projects they aren't wanted in. If you want it, you have to have a direct reference to a package that contains it.

@Code-Grump
Copy link
Contributor Author

Regarding 8: the nullable stuff is only really useful to aid code that is being written as an extension of the class. I noticed that our classes are marked as partial, so I assume somebody will be referencing them somewhere and would benefit from having the nullable information properly presented. It additionally helps me (and everybody else who has to inspect the generated code) to detect where we maybe aren't handling nullable references properly.

Ideally we'd not need to complicate the code with the feature toggle, however we are still supporting much older C# versions without nullable reference support.

@Code-Grump
Copy link
Contributor Author

Regarding 13: This was a bug in equality checks involving type hierarchies, a particularly prickly topic I have yet to find an airtight pattern for. With the equality checks fixed, the test methods are updated as you type.

@Code-Grump
Copy link
Contributor Author

Regarding #31: I'm bumping up against the limitations of the Gherkin parser, most notably a lack of precise token positions used for source-mapping.

It would be really good to have an implementation that used the Roslyn data structures, so I wouldn't have to keep marshalling data between structures (sometimes introducing off-by-one bugs).

Dependencies are a pain-point, having to be embedded in the NuGet package directly and loaded as an analyzer (even when they're not). The model really wants an all-in-one assembly.

@gasparnagy
Copy link
Contributor

@Code-Grump I added a list of review comments in the PR description and based on your commits and comments I tried to mark the ones that are handled now. Please review and if any other is also fixed, mark it.

Here are my reponses to the other notes:

Regarding 32: it's because there's a discrepancy between our current implementations: MSTest and xUnit emit these examples, where the nUnit handling omits them. They're excluded early in the pipeline, too late for the framework handler to get involved. The easiest solution was to make it a toggle and have the NUnit package switch it off.

Ok. I see. These differences are not intentional, but we emited/excluded examples depending on the capabilities of the runner. I don't remember the details, but I can check. So you can have this as an internal parameter (e.g. a bool prop on the test fw implementation classes), but I would not expose this as a configuration parameter for the end user. Let's keep it as an internal decision whether we want or do not emit the ignored examples.

Regarding 3: [...] The major downside of this would be that you'd not be able to influence generation settings on a per file/folder basis as you can with .editorconfig.
My personal preference is to migrate users to .editorconfig settings, following the convention of all Roslyn components. It's a new generator with new rules!

I was thinking this over once more. I understand your points, but I would vote against the .editorconfig still. My arguments are:

  • The .editorconfig was designed for configuring the editor (indent sizes, tabs, colors, etc). Our generator settings have nothing to do with the editor. So this seems to me a conceptual misuse. I don't know why the Roslyn gen folks picked up the .editorconfig, but I don't think that was a good decision either. This is not editor config.
  • For us, Roslyn gen is an internal technical dependency. Our users do not need to know about this technology. We might need to even move on to another one at some point or keep both the MsBuild and the Roslyn one in parallel. The way our users configure the behavior or Reqnroll should not be influenced by the way how Roslyn code gen tools are usually configured.
  • For us, the configuration should be as predictable and transparent as possible to avoid confusions when the tests are executed, the configuration decisions should be therefore easily accessible to anyone in the team using Reqnroll. Because of that, in general the cleanest approach is to make these config decisions per project. The per-folder overrides of .editorconfig and the fact that .editorconfig can even be specified outside of the project level or even outside of the git repo is not practical.
  • The generator config is only a part of our config options. There are also runtime config options, but even more importantly there are shared config (needed both by the runtime and the generator), a good example for this is the language settings. For runtime, the concept of .editorconfig is not matching as there are no "folders", so the .editorconfig could not be used as the only config platform.
  • There json schema provides a nice editor experience for the json config files, but we don't have that for .editorconfig, I think.
  • The configuration files have to be processed by the Reqnroll code (runtime and gen) and also by the IDE integrations as well. So any strategic change we do in the configuration might require a extra work for the IDE integrations as well.

Regarding 8: the nullable stuff is only really useful to aid code that is being written as an extension of the class. I noticed that our classes are marked as partial, so I assume somebody will be referencing them somewhere and would benefit from having the nullable information properly presented. It additionally helps me (and everybody else who has to inspect the generated code) to detect where we maybe aren't handling nullable references properly.
Ideally we'd not need to complicate the code with the feature toggle, however we are still supporting much older C# versions without nullable reference support.

I see. The classes were generated as partial as a kind of back door for special temporary hacks. I don't think anyone uses the partial behavior of the classes.

My fear is that the different test framework versions are annotated differently for nullability, so making sure that there are no warnings is an endless fight that has to be redone every time a new test fw version or .NET version comes out. Also when we make a change ourselves but we might forget to verify some combinations when this causes a warning.

Many projects are configured in a way that warnings are treated as errors, so if we make a mistake here (in some combination a warning is produced), the users will have no other option than generally ignore the nullable warning for their Reqnroll project, so their own real code will not be checked. So my feeling is that the risk of the harm for an emitted warning is more severe then the benefit we get from the nullable checks in the generated code.

But we can make a test period and see if we can get rid of this warnings for all combinations.

Regarding 31: I'm bumping up against the limitations of the Gherkin parser, most notably a lack of precise token positions used for source-mapping.

Could you please elaborate on this a bit more in detail?

Regarding 31: It would be really good to have an implementation that used the Roslyn data structures, so I wouldn't have to keep marshalling data between structures (sometimes introducing off-by-one bugs).

The Gherkin parser is designed in a way that you can provide your own AST builder that builds the structure you want/need. So it would be possible to use the Gherkin parser that is configured with a special AST builder that builds the structures you need immediately during parsing. The AST builders should contain no logic, so by replacing the built-in AST builder with your own one will not change the parsing behavior. (Currently we might do a few extra validations in the AST builder that you would need to duplicate though.)

Regarding 31: Dependencies are a pain-point, having to be embedded in the NuGet package directly and loaded as an analyzer (even when they're not). The model really wants an all-in-one assembly.

I understand this, but then we would need to find a way to apply those few additional validation that I mentioned in the review note in your gen as well. As there are only 4 of them, duplicating the code is also acceptable for me. I just don't want to lose them.

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.

4 participants