ReSharper DevGuide

Edit page

Activation

Components are only included into the Component Model if the zones they belong to are “active”. Zones are not activated by default, and must be activated by an activator class, which is a component decorated by the [ZoneActivator] attribute, and implementing IActivate<TZone> for each zone it activates.

If a zone is not activated, any components that require that zone will not be available in the application. It’s worth noting that if none of the components in an assembly are available, then the assembly itself isn’t even loaded into memory, as the catalogues that the Component Model use are based on caching and direct IL metadata reading, rather than Reflection.

Implementation

An activator class implements the IActivate<TZone> interface, which is defined as:

public interface IActivate<TZone> where TZone : IZone { bool ActivatorEnabled(); }

The class can implement the interface multiple times, once for each zone it activates. During application startup, the ReSharper Platform will query the activator for each implementation of IActivate<TZone> and call the ActivatorEnabled method. If the class returns true, the zone is marked as active.

When activating a zone, the ReSharper Platform activates that zone, and all zones that inherit from it. For example, if IActivate<IPsiLanguageZone>.ActivatorEnabled() returns true, the IPsiLanguageZone is now activated, as are all zone definitions that inherit from IPsiLanguageZone, such as IClrPsiLanguageZone, ILanguageCSharpZone and ILanguageJavaScriptZone.

Typically, a zone activator aligns with a Product or Feature, and unconditionally enables the zone definition that declares the Product or Feature via the [ZoneDefinitionProduct] attribute. It should also activate the zones that the Product or Feature zone requires. It is not an error to activate the same zone from multiple activators. E.g. if both ReSharper and dotPeek need the ExternalSourcesZone, they should both activate it - if they don’t, and they end up being the only product installed into the ReSharper Platform, the required zone won’t be active and the Product or Feature’s requiring zones won’t be loaded.

[ZoneActivator] class DotPeekZoneActivator : IActivate<DotPeekProductZone>, IActivate<ExternalSourcesZone>, IActivate<NavigationZone>, IActivate<DaemonZone>, IActivate<ILanguageCSharpZone> { public bool ActivatorEnabled() { return true; } }

There is usually little or no logic in the implementation of ActivatorEnabled. Generally speaking, if a product is installed, it should be activated. Activation is separate to licensing, trial versions and user disabling of a product or feature - this is handled separately by the ReSharper Platform. Unlicensed, expired, or disabled zones are removed from the list of activated zones before components are filtered into the container.

However, it is perfectly reasonable to implement some logic in this method, for example, the InternalModeProductZoneActivator class will activate the IInternalVisibilityZone (for Internal Mode) only if the /Internal command line argument is supplied.

Explicit implementation

The activator can implement the IActivate<TZone> interfaces explicitly, to allow for different implementations for each zone:

[ZoneActivator] public class MyZone : IActivate<IZone1>, IActivate<IZone2> { bool IActivate<IZone1>.ActivatorEnabled() { return ShouldActivateZone1(); } bool IActivate<IZone2>.ActivatorEnabled() { return ShouldActivateZone2(); } // ... }

Activator class as component

Similarly, zone activator classes are components (ZoneActivatorAttribute derives from EnvironmentComponentAttribute, so they are components that get created very early in the application lifecycle), which means they can accept dependencies in the constructor. For example, a Feature that is intended for use only in Internal Mode can do something like:

[ZoneActivator] public class MyInternalModeFeatureZonesActivator : IActivate<IMyInternalModeFeatureZone> { private readonly InternalModeProductZoneActivator internalModeActivator; public MyInternalModeFeatureZonesActivator(InternalModeProductZoneActivator internalModeActivator) { this.internalModeActivator = internalModeActivator; } public bool ActivatorEnabled() { // Only enable this zone if the internal zone is going to be enabled return ((IActivate<IInternalVisibilityZone>)internalModeActivator).ActivatorEnabled(); } }

This is perhaps a contrived example - an internal only Feature would be better requiring IInternalVisibilityZone in its zone, rather than handling it in this manner. However, it is an example that serves as a reminder that a zone activator is a component, and follows normal Component Model rules.

Component Model considerations

Since the activator class is a standard component, it naturally follows normal Component Model creation rules. This means that an activator class must itself belong to a zone that is already active!

In other words, the zone activator is only available if it has a zone marker. The zone marker can be an empty zone marker, in which case the activator is always active, and always created. Or it can require one or more of the default active zones (see below), in which case the activator component will only be active if all of the required zones are active.

For example, the ReSharperZonesActivator class, which lives in the JetBrains.ReSharper.Product.Application.Product namespace, has the following zone marker:

namespace JetBrains.ReSharper.Product.Application { // This zone is only active when IVisualStudioZone is active. // So any activator class in this zone is also only created when // running in Visual Studio. [ZoneMarker] public class ZoneMarker : IRequire<IVisualStudioZone> { } }

Note the namespace of the marker is above the namespace of the activator. This means that the activator class belongs to the zone definitions declared in the marker’s dependencies, namely IVisualStudioZone. So, the ReSharper product zone activator is only active when the IVisualStudioZone zone is itself active. This zone is an environmental zone, and is created and activated before the environment container is composed.

Disabling activators

A zone activator can take a dependency on a zone, using IRequire<TZone> in the same way zone markers do. This allows for a zone activator to be disabled if any of these required zones are disabled, for whatever reason - unlicensed, expired or explicitly disabled by the user.

It is not immediately obvious why a zone activator would wish to be disabled.

This happens naturally if an activator’s zone marker’s dependencies aren’t satisfied. However, zone markers for zone activators are limited in that they can only require the bootstrap zones that are used when creating the environment component container (see the section on default active zones). It also happens if any of the zones being activated are no longer licensed.

The main reason for wanting to disable a zone activator is so that supporting zones are not activated unnecessarily. A common pattern is for a zone activator to require a dependency on the main zone it is trying to activate. If that zone has been disabled, for whatever reason, the activator is also disabled, and supporting zones are not activated.

For example, the ReSharper C++ activator requires the C++ product zone, but also activates code editing, daemon and navigation zones. If the C++ product zone feature is disabled, the C++ activator would still activate code editing, daemon and navigation. If no other Features or Products used those zones, they are unnecessarily activated (and probably unlicensed). By requiring the C++ product zone, the ReSharper Platform will disabled the C++ activator if the C++ product zone is disabled. And now, the code editing, daemon and navigation zones are only activated if another activator needs them.

[ZoneActivator] class CppProductZonesActivator : IActivate<ILanguageCppZone>, IActivate<ICodeEditingZone>, IActivate<DaemonZone>, IActivate<NavigationZone>, IActivate<ICppProductZone>, IRequire<ICppProductZone> { public bool ActivatorEnabled() { return true; } }

Default active zones

The ReSharper Platform provides several default zones that are automatically activated based on the current host’s environment. These zones are used to bootstrap the zone activators. An activator’s zone marker can require one or more of these zones to allow for conditionally including and excluding zone activators based on the host environment.

For example, if a zone needs to be run inside a particular version of Visual Studio, the zone activator’s zone marker can require the Visual Studio zone. If the current host is an appropriate version of Visual Studio, the Visual Studio zone is activated, the zone marker’s dependencies are satisfied, and the zone activator is created and finally activates the target zone.

The default zones derive from IHostSpecificZone and are:

  • An environment zone, representing the current running host environment. These zones derive from IEnvironmentZone
    • IConsoleZone when the host is a command line utility. Derived interfaces represent the different supported console applications, such as InspectCode and DuplicatesFinder.
    • IStandaloneUIZone for when the host is a standalone GUI application, such as dotPeek. Again, derived interfaces represent standalone applications.
    • ITeamCityZone for when the host is running inside TeamCity.
    • IVisualStudioZone for when the host is Visual Studio.
  • CLR version zone - ISinceClr2Zone or ISinceClr4Zone. Components that are built for .net 4 should require ISinceClr4Zone. Note that ISinceClr4Zone has a dependency on ISinceClr2Zone, so all CLR 2 components are also available for .net 4.
  • CPU architecture zone, representing 32 and 64 bit - IIntelCpuArchitectureZone and IAmd64CpuArchitectureZone.
  • ITestsZone to indicate that the code is running under test.

The Visual Studio zones make extensive use of zone inheritance to model different versions. Each version has two interfaces, a “since” zone and a “just” zone, e.g. ISinceVs10Zone and IJustVS10Zone.

The “since” zones inherit from the previous version’s “since” zone, so ISinceVs12Zone derives from ISinceVs11Zone, which derives from ISinceVs10Zone, etc. When running in VS12 (Visual Studio 2013), the ISinceVs12Zone is active, and so is ISinceVs11Zone and ISinceVs10Zone. If a component uses a Visual Studio feature that works across versions, it should require the zone at which the feature was introduced. For example, if the feature was introduced in Visual Studio 2012 (VS11), the component should require ISinceVs11Zone. When running in Visual Studio 2012 (VS11) or 2013 (VS12), the ISinceVs11Zone is active, and the component runs. If running in Visual Studio 2010 (VS10), the ISinceVs11Zone isn’t active, and the component isn’t loaded.

The “just” zones inherit from the current version’s “since” zone, so IJustVs12Zone derives from ISinceVs12Zone. If code is written that targets just a single version of Visual Studio, it should require the “just” zone. For example, if code targets just Visual Studio 2010 (VS10), it should require IJustVs10Zone. When run in Visual Studio 2012 (VS11), the IJustVs10Zone isn’t active, and the component isn’t loaded.

Inheritance and activation

When declaring a zone definition using the [ZoneDefinition] attribute, dependencies can be declared either by implementing the IRequire<TZone> interface, or by inheriting directly from an existing zone definition. There is no difference between the two with regard to dependencies - when applying a zone to a class or namespace, that zone and all of its dependencies must be active before the component is allowed.

The difference comes with activation. When a zone inherits from another zone definition, it is automatically activated when the base zone is activated. For example, the ReSharperZonesActivator class implements IActivate<IPsiLanguageZone>.ActivatorEnabled() and returns true. This activates the IPsiLanguageZone and all zone definitions that inherit from it, including IClrPsiLanguageZone and therefore ILanguageCSharpZone. Any new code that is added that implements a zone inheriting from IPsiLanguageZone, or IClrPsiLanguageZone will also get automatically enabled.

Dependencies registered via IRequire<TZone> are not automatically enabled. These zones must be enabled via another activator, or via activation of their base zone definitions.

The Visual Studio zones are very hierarchical (see above). The “just” zones are the zones that are activated. Because the “just” zone derives from a chain of “since” zones, when a “just” zone is activated, the derived “since” zones are also enabled. So when IJustVs12Zone is activated, it’s inherited ISinceVs12Zone is also activated, as are ISinceVs11Zone, ISinceVs10Zone, IVisualStudioZone, etc.

Auto-enabled zones

A zone definition can be declared as auto-enabled, if it makes sense that the zone is always available, regardless of configuration, installed products, etc. This is handled with the ZoneFlags parameter to the [ZoneDefinition] attribute.

[ZoneDefinition(ZoneFlags.AutoEnable)] public interface IMyZone { }

This has the same effect as a simple implementation of IActivate<TZone>.ActivatorEnabled() that always returns true. However, it doesn’t allow for finer control, such as activating supporting zones - it will only activate IMyZone. If IMyZone requires other zones to be active, an activator class should be used.

 

Last modified: 10 July 2017