Skip to content

Conversation

DustinCampbell
Copy link
Member

@DustinCampbell DustinCampbell commented Feb 20, 2025

Important

This contains RazorSDK breaking changes! I've prepared a fix for these changes that can applied when the SDK build fails: DustinCampbell/sdk@4e955e1

Warning

This is a big change with a lot of commits. To facilitate code reviews, I've tried to keep the commit history informative. Most commits have explanatory messages and product and pure test changes are in separate commits.

The majority of the changes are in the compiler, so this will require two compiler reviews from @chsienki, @333fred, @jjonescz, or @jaredpar.

CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2647163&view=results
Test Insertion: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/612761

Summary

  • Refactor and simplify RazorParserOptions API and its builder pattern.
  • Refactor and simplify RazorCodeGenerationOptions API and its builder pattern.
  • Introduce RazorProjectEngineBuilder extension methods to configure parser and code generation options.
  • Consolidate or remove most IConfigureRazorParserOptionsFeature and IConfigureRazorCodeGenerationOptionsFeature implementations.
  • Clean up directive configuration to avoid using ICollection<T> and IReadOnlyDictionary<TKey, TValue>.
  • Add ParserOptions and CodeGenerationOptions properties to RazorCodeDocument (and remove them from RazorCodeDocument.Items)
  • Ensure that RazorCodeDocument is always created with valid parser and code generation options.
  • Remove the legacy IRazorParserOptionsFeature and IRazorCodeGenerationOptionsFeature interfaces and their implementations. These served as fallbacks when a RazorCodeDocument didn't have parser or code gen options. However, that's no longer the case, so the fallbacks can be removed.
  • Remove the IRazorParserOptionsFactoryProjectFeature and IRazorCodeGenerationOptionsFactoryProjectFeature interfaces and their implementations. These were only used by RazorProjectEngine when creating a RazorCodeDocument and their functionality has been rolled into RazorProjectEngine.
  • Provide several helper methods in the compiler and test infrastructure to make it easy to create a RazorCodeDocument from a RazorProjectEngine.
  • Unify the compiler test infrastructure for RazorProjectEngine-based tests. The same test infrastructure is shared across the Language and "Extensions" tests
  • Introduce a RazorCodeDocumentProcessor helper class for executing particular compiler phases and passes with a RazorCodeDocument.
  • Graduate many copied-and-pasted test helpers to shared extension methods.
  • Update 100s of tests to always create RazorCodeDocuments from a RazorProjectEngine. This ensures that the options are always configured correctly.
  • Rationalize the RazorCodeDocument.Create(...) API. Realistically, this should rarely be used. It's always best to acquire a RazorCodeDocument from a RazorProjectEngine.
  • Remove #nullable disable where it's easy to do. 😄

Tip

If you wish to only review product changes, you can skip the following commits. Note that 1, 2, and 15 are strays, but 3-14 are grouped together in the commit history.

  1. Remove TestRazorCodeDocument.Create(Source, Imports) test helper (4955122)
  2. Fix DefaultRazorParsingPhaseTest (45e62cb)
  3. RazorProjectEngineTestBase: Remove CreateEngine() (ab97efc)
  4. ModelDirectiveTest: Update to create code document from project engine (5e1cd9d)
  5. ModelDirectiveTest: Fix incorrect test assertions (c6382d8)
  6. Refactor to share more test infrastructure (22ec9bd)
  7. PageDirectiveTest: Move to new test infrastructure (4d5954c)
  8. Clean up Version1_X tests (ff5c526)
  9. Clean up Version2_X tests (601d9be)
  10. Finish cleaning up MS.ANC.Mvc.Razor.Extensions.Test (fa98b37)
  11. Refactor and improve the testing API of RazorProjectEngineTestBase (209fa7e)
  12. Rename IntermediateNode test helpers and unneeded remove null assertions (55a3731)
  13. Remove RazorCodeDocument.Create(...) calls from compiler tests (8f2a0f3)
  14. Remove RazorCodeDocument.Create(...) calls from tooling code (9e4db21)
  15. Clean up and document RazorProjectEngineTestBase (2243678)

- Remove unused usings
- Use ArgHelper.ThrowIfNull throughout
- Annotate for nullability
There are two RazorProjectEngineBuilderExtensions classes in the compiler in different namespaces. This merges the two, which may be a breaking change for the RazorSDK, which uses the SetCSharpLanguageVersion extension method.

This change also merges the RazorProjectEngineBuilderExtensionsTest files and tweaks the SetCSharpLanguageVersion test to be a bit more useful.

Finally, a few using directives have been removed since SetCSharpLanguageVersion now resolves from a different namespace.
Adds ConfigureParserOptions and ConfigureCodeGenerationOptions extension methods for RazorProjectEngineBuilder
Replaces uses of ConfigureRazorCodeGenerationOptions class with call to ConfigureCodeGenerationOptions(...) helper.
Replaces uses of ConfigureRazorParserOptions class with call to ConfigureParserOptions(...) helper.

This is a much larger change because there are many more uses of this class in both product and test code. I've updated each of these mechanically and removed "#nullable disable" when it wouldn't introduce warnings.
Most IConfigureRazorParserOptionsFeature implementations are trivial and can be replaced with calls to  the RazorProjectEngineBuilder.ConfigureParserOptions(...) helper method.
Most IConfigureRazorCodeGenerationOptionsFeature implementations are trivial and can be replaced with calls to  the RazorProjectEngineBuilder.ConfigureCodeGenerationOptions(...) helper method.
The goal of this rewrite is to avoid exposing collection interfaces and making consumers understand the semantics of the logic. Instead, it now has a simple API to add and get directives.
Change the signature of AddDirective to take a ReadOnlySpan<string> and make it internal. This API is not used by the RazorSDK, so it shouldn't be considered a breaking change.

In addition, I've removed some code duplication for features that need to get a feature or create one if it doesn't exist.
AddDefaultImports is only used by tests and isn't consumed by the RazorSDK. So, it isn't a breaking change to move out of product code.
Three methods on RazorProjectEngineBuilderExtensions aren't actually used by anything in Razor or the RazorSDK: ConfigureClass, SetBaseType, and SetNamespace. These have been removed.

In addition, AddTargetExtension is only used in Razor and not the RazorSDK. So, I've changed its accessibility to internal.
Both IConfigureRazorParserOptionsFeature and IConfigureRazorCodeGenerationOptionsFeature support Order properties that should be used. ConfigureDirectivesFeature sets Order to 100 to ensure that it runs last. So, this change honors that.
- "designTime" is always false for DefaultRazorCodeGenerationOptionsFeature
- "designTime" is always false for DefaultRazorParserOptionsFeature
- "fileKind" is always null for DefaultRazorParserOptionsFeature
This is a larger change that rationalizes the RazorCodeGenerationOptions and its builder APIs.

RazorCodeGenerationOptions:
    - Added "Wither" methods
    - Removed Create(...) methods
    - Removed ToBuilder() method

RazorCodeGenerationOptionsBuilder:
    - Replaced Configuration property with LanguageVersion.
    - Converted SetDesignTime method to property.
    - Removed excess constructors.
    - Renamed Build() method to ToOptions()

The remaining files changed are largely mechanical changes responding to the API changes.
This is a larger change that rationalizes the RazorParserOptions and its builder APIs.

RazorParserOptions :
    - Introduced RazorParserOptionsFlags to hold boolean values.
    - Convert CreateDefault() method to a Default property.
    - Added "Wither" methods
    - Removed Create(...) methods

RazorParserOptionsBuilder:
    - Remove Configuration property.
    - Converted SetDesignTime method to property.
    - Removed excess constructors.
    - Renamed Build() method to ToOptions()
    - Use RazorParserOptionsFlags for all boolean values.
    - Add FeatureFlags property to hold RazorParserFeatureFlags.

The remaining files changed are largely mechanical changes responding to the API changes.
RazorProjectOptionsBuilder is initialized with CSharpParseOptions.Default, so it's unnecesary to set that value.
This change nests RazorParserOptions' builder and flags within it. The flags have been made private along with the RazorParserOptions constructor.
This change nests RazorCodeGenerationOptions' builder and flags within it. The flags have been made private along with the RazorCodeGenerationOptions constructor.
This change pulls the RazorParserFeatureFlags values into RazorParserOptions.
This change removes DefaultRazorParserOptionsFactoryProjectFeature and DefaultRazorCodeGenerationOptionsFactoryProjectFeature and their associated interfaces. The purpose of these features can be achieved within RazorProjectEngine.
This makes RazorCodeGenerationOptions consistent with RazorParserOptions (and their builders).
This change converts the following extension methods to RazorCodeDocument instance methods:

- GetParserOptions()
- GetCodeGenerationOptions()
- SetParserOptions()
- SetCodeGenerationOptions()

The options instances are now stored directly as fields on RazorCodeDocument rather than in the Items collection.
This method simply calls RazorCodeDocument.Create(Source, Imports). So, the test helper is unnecessary.
Because RazorCodeDocument now owns its ParserOptions and CodeGenerationOptions, it is generally important to create a RazorCodeDocument by calling RazorProjectEngine.CreateCodeDocument(...) in order to configure the options correctly.
…r passes

Call RazroProjectEngine.CreateCodeDocument(...) rather than RazorCodeDocument.Create(...) within the compiler to ensure in is created with the configured options.
There's a RazorProjectEngineTestBase.CreateEngine() method that returns a RazorEngine and is only used by a handful of tests. These tests should be using RazorProjectEngine to create RazorCodeDocuments. So, this change updates those tests and removes CreateEngine(). In addition, several helper methods have been moved to MS.ANC.Razor.Test.Common as extension methods to prepare for further test clean up.
This change updates MS.ANC.Mvc.Razor.Extensions.Test\ModelDirectiveTest to create and use RazorCodeDocuments from the RazorProjectEngine. In addition, this change adds helpers for creating design-time RazorCodeDocuments.
Both of the design-time tests fail after being updated to use code documents from the project engine. The reason is that the test was initially buggy. Previously, the design-time tests created a RazorProjectEngine with options configured for design-time. However, the RazorCodeDocument was created without those options. So, DocumentIntermediateNode.Options.DesignTime wouldn't be set, and ModelDirective.Pass.ExecuteCore(...) would follow a runtime code path. Now that the RazorCodeDocument is correctly configured, it takes the correct design-time code path and the assertions need to be updated.

IOW, the test was wrong and now it's correct.
@DustinCampbell DustinCampbell requested review from a team as code owners February 20, 2025 23:37
@DustinCampbell DustinCampbell changed the title Make ParserOptions and CodeGenerationOptions properties on RazorCodeDocument and rationalize options configuration Add ParserOptions and CodeGenerationOptions properties to RazorCodeDocument and rationalize options configuration Feb 20, 2025
@chsienki chsienki self-assigned this Feb 21, 2025
@333fred 333fred self-assigned this Feb 21, 2025
Copy link
Member

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

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

Tooling changes look good. PR description is simply a work of art, 5/7 perfect!

Copy link
Member

@333fred 333fred left a comment

Choose a reason for hiding this comment

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

Generally LGTM. Did notice one thing that I think was just a typo.

Copy link
Contributor

@ryzngard ryzngard left a comment

Choose a reason for hiding this comment

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

Tooling side LGTM

11/10 would review again

Copy link
Member

@chsienki chsienki left a comment

Choose a reason for hiding this comment

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

Looks great, this is a really nice change. Thanks!

A normal null-coalescing expression works just fine.

Co-authored-by: Fred Silberberg <[email protected]>
@DustinCampbell
Copy link
Member Author

/azp run

Copy link

Azure Pipelines successfully started running 2 pipeline(s).

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.

6 participants