External annotations when testing highlightings

What is the right way to load external annotations for a test when using CSharpHighlightingTestBase?

Currently for normal unreleased work I load them by providing paths in a custom IExternalAnnotationsFileProvider (with [EnvironmentComponent(Sharing.Product)]).
But that class is never instantiated during the test, so I assume test environment does not use the same container settings.

Comment actions Permalink

Nevermind, with the help of dotPeek symbol server I've resolved it -- if I change the IExternalAnnotationsFileProvider lifetime to [PsiComponent], it all works.
Not sure why I didn't use that to start with -- probably a stupid copy from a wrong place.

Question: is there a blog somewhere that discusses all those scopes?
([PsiComponent], [EnvironmentComponent], etc)

Comment actions Permalink

There are essentially four scopes for components, and the two interesting ones are documented in the section about the Component Model: http://confluence.jetbrains.com/display/NETCOM/2.02+Component+Model+%28R8%29

You can have EnvironmentComponent, ShellComponent, SolutionComponent(*) and ProjectComponent. Plugins will generally use ShellComponent and SolutionComponent. These scopes are nested, so if a component isn't found in the current scope, it looks in the parent scope, so project scope is nested inside solution, which is nested inside shell, which is inside environment. A shell component has the lifetime of the shell, which is usually the lifetime of Visual Studio, except it's also terminated and restarted if someone suspends and resumes ReSharper in the VS options. The environment scope does last for the duration of the Visual Studio lifetime, and is really intended for components that are required to set up the environment and bootstrap the shell. There shouldn't really be any "product" functionality here, just infrastructure. The two instances of IExternalAnnotationsFileProvider in the ReSharper codebase are marked Environment, but I don't know why (and I should, as I did it ?:|). I think they'd be fine as SolutionComponents.

ProjectComponent is an interesting and rarely used component. It allows for a single component per project, while SolutionComponents give you a single component per solution. It also allows you to inject IProject, rather than having to get it from data you're operating on. It's generally used for project level settings and properties (e.g. project level live templates, or C# language level).

PsiComponent is a derived instance of SolutionComponent. So, a PsiComponent IS a solution component - it doesn't provide a new scope. The reason for having derived components (and there are quite a few of them) is to allow filtering on these components. So if a manager class is interested in all PsiComponents, it can filter based on the attribute, but the attribute also controls lifetime. Instead of having two attributes, one controlling scope/lifetime, and the other acting as a marker, the derived attribute is used to mark the component as a PsiComponent, which has a specific lifetime, and it becomes an implementation detail that it's actually solution scope. And in fact, PsiComponent isn't used other than as a solution component attribute - it's not retrieved separately to scope and lifetime management.

I'm a little surprised that your EnvironmentComponent doesn't work. I'm not entirely sure why, but I'm guessing that plugins can't host Environment components in tests - the means for getting plugin and test components loaded adds them to a shell scoped catalogue, and I suspect that the environment container won't see them in a lower level catalogue. It would probably work when loading the plugin normally, as components are then added to a collection of global catalogues, which is then filtered. Similarly, it's not an issue for testing ReSharper itself, since it uses a different mechanism to load components (the catalogues are constructed based on xml config describing the product). I'll need to look into it. On the plus side, plugins should rarely need to use Environment components, as it's intended for infrastructure rather than features.

As an aside, a useful tip is that you can export components from your test code, allowing you to define the annotations file provider in the test code and rely on the extension manager in the production code.

(*) There's also SolutionInstanceComponent. As I understand it, this is an implementation detail of creating a solution, and should be happily ignored.

Comment actions Permalink

That's a really awesome answer, thanks!
It seems I totally missed the documentation -- I'll read through it, should save some time reading the decompiled code and asking questions).


Please sign in to leave a comment.