What are my options on discovery of custom attributes used in External Annotations xml?

If I create an external annotations XML file that references some custom attributes, e.g.

<member name="T:Microsoft.Activities.Build.WorkflowBuildMessageTask">
  <attribute ctor="M:MyAttribute.#ctor">
    <argument>Xyz</argument>
  </attribute>
</member>

What are the options for the location of MyAttribute declaration so that I can query its PositionalArgument properly?

I would prefer either:

  1. In my extension's assembly
  2. Nowhere, just constructed by me as fake declaration


Is either possible?

Cheers,
Andrey

10 comments
Comment actions Permalink

ReSharper should treat external annotation attributes transparently. That is, when you use the API to get at the attributes of an assembly, type or type member, ReSharper will effectively merge the external annotation attributes with any attributes that are actually applied to the element. So you should just be able to look for an attribute of the required name, and then examine its positional arguments as if it were actually applied.

0
Comment actions Permalink

In addition to what Matt said, I used External Annotations to add the Obsolete attribute to some API and trick some logging code to appear "greyed out".
I think that any resolved (attribute) type can be applied, regardless of where it is located.

0
Comment actions Permalink

You say
I think that any resolved (attribute) type can be applied, regardless of where it is located.

But the fact whether it is resolved on it depends on where it is located, doesn't it?
How can I add a type included in my plugin assembly to the "resolved types" space?

0
Comment actions Permalink

Thanks. Yes, I understand the merging process.
However access to the positional attributes requires the attribute constructor to be resolved properly -- so my question is where does it search for constructors.

0
Comment actions Permalink

But the fact whether it is resolved on it depends on where it is located, doesn't it?
How can I add a type included in my plugin assembly to the "resolved types" space?


Assembly with applied attributes should be referenced in TARGET solution, not in plugin itself.

0
Comment actions Permalink

Assembly with applied attributes should be referenced in TARGET solution, not in plugin itself.

That's exactly what I am asking about. Let's say a plugin provides some support for new annotation, for example ReadOnlyAttribute.
Plugin can include corresponding annotations for the BCL.

However, current solution does not have to include this attibute, and even if it does, it is no guaranteed to be in any particular namespace (as annotation namespaces are configurable).

It is exactly the same situation as with JetBrains.Annotations.StringFormatAttribute -- you get highlighting for string.Format even if it there is no StringFormatAttribute  in your solution.
0
Comment actions Permalink

Hi Andrey. I'm not sure what you're asking here. If you're trying to apply an attribute via an external annotations xml file, then as stated, the attribute is effectively merged, and from a ReSharper plugin's point of view, when you get the attributes for a type or type member, your attribute will be included in the list. The annotations xml file contains the full namespace of the attribute to apply, the constructor to use and the parameters used in the constructor - you can just look at the returned attributes and all the information will be there.

If you're applying an attribute in the current solution, then your plugin can just look at the short name or the fully qualified name, and it's up to your plugin what you do. The attribute needs to be either referenced in an external assembly, or defined in the current solution, but either way, the plugin will still just see it as an attribute applied to the type member.

This is essentially what ReSharper itself does, but ReSharper looks for a list of known namespaces, rather than just going by the short name. The list is stored in the settings of the solution, and will be the standard JetBrains.Annotations, by default. If the user uses a quick fix or context action to add the annotations source file, the annotations are added with the project's default namespace, which is added to the list of known namespaces. When showing the options page, ReSharper will search for all definitions of CanBeNullAttribute and NotNullAttribute. Any namespace that has both of those are added to the dialog, unchecked by default.

So, with external annotations, the namespace is fixed, in the xml file. With attributes applied in your source code, you can either ignore the namespace, or use the same list that ReSharper uses, by getting the namespaces from the CodeAnnotationSettings settings object. You can take a look at CodeAnnotationsCache.UpdateAnnotationNamespaces to see how to access the namespace list.

Does this give you the info you're after?

0
Comment actions Permalink

Thanks for the detailed reply.
I think I now understand pretty well what's happening here.

If I use a name of the attribute in an external annotations XML, one of the following applies:

  1. If this attribute is in my solution (or any referenced libraries) it is fully loaded
  2. If this attribute is in JetBrainsAnnotations.dll in ReSharper folder, it is fully loaded (even if it is not referenced by the solution, see JetBrains.ReSharper.Psi.Impl.Reflection2.ExternalAnnotations.ExternalAnnotationsModuleFactory).
  3. If it is neither in solution or JetBrainsAnnnotations.dll, it would be loaded, but you can't get its arguments


So my options are:

  1. Create a fake IPsiModule that would provide this attribute
  2. Patch JetBrainsAnnotations.dll (does not sound like a good idea, but should work)


I'll try the first approach and see if that's feasible.

Thanks again!

0
Comment actions Permalink

Ah, yes, you're right. I see where the confusion has come from. Normally, external annotations Just Work (tm). The xml file contains the name and namespace of the attribute, declares which constructor to use, and provides constant values for the constructor arguments. As far as ReSharper and plugins are concerned, these attributes are applied just as though they had been compiled into the referenced assembly, and you can get access to name, namespace and (the point we've been talking about) constructor arguments.

However, unless the current solution has some kind of reference to an assembly that declares the attribute, the annotation will be unresolved.

This isn't an issue for ReSharper's annotations, as they all come from JetBrains.Annotations.dll and, as you point out ExternalAnnotationsModuleFactory adds a "fake reference" to the JetBrains.Annotations.dll from the product install directory. You can also use attributes from the BCL (such as ObsoleteAttribute) without any issues, because you've always got a reference to mscorlib or system.dll and the attribute will resolve.

If you're adding a custom attribute as an external annotation, then yes, you do need to add your own IPsiModule in the same that ExternalAnnotationsModuleFactory adds a reference to JetBrains.Annotations.dll. (This isn't a reference in the same way as a project's assembly reference - it doesn't affect the build at all, but simply makes the assembly available to ReSharper's type resolution mechanisms).

0
Comment actions Permalink

Hmm, I am having problems with getting this to work. PsiModule is pretty easy to add, and it almost works.
However since my annotation assembly in not actualy referenced by mscorlib/System/etc, the attribute is not accepted by the scope.

Basically it is resolved, but then Scope.Accepts() call in JetBrains.ReSharper.Psi.Caches.SymbolCache.SymbolScopeBase.GetTypeElementsByCLRName returns false, and so the resolution does not get through.

JetBrains.Annotations is also rejected by this, but because there is a special case for it in ExternalAnnotationAttributeInstance (which resolves it relative to its own module), it does work.

Any ideas for a workaround?

(Btw I switched to R# 8.2 now)

0

Please sign in to leave a comment.