Customising/extending behaviour of HtmlUnknownAttributeInspection

Answered

I have two sets of attributes that I would like the inspection to ignore.

One set is fixed (e.g v-a=.., v-b=..), but the other is kinda dynamic (:class=.., :style=..) and I would still like the inspection to handle (but with a different name).

At first, I tried modifying the inspection's addEntry method during postStartupActivity. This worked well, but it has two major drawbacks:

  1. the entries added are permanently stored in the inspection's settings - I don't rather not have the user see this as a customisation
  2. this flow has some big drawbacks: I cannot handle cases where an attribute is only allowed if another specific attribute is there, nor can I handle the dynamic set of attributes I mentioned already


So then I tried a different approach. The plan was to disable the original inspection and add a modified one.

I couldn't find the right way to disable/unload the inspection; I tried using the suppress inspections EP, but I believe this isn't what I would want (and also it didn't seem to work).

 

Now here's the funny part. So I subclass the inspection, modify the behaviour and add it back with inspectionToolProvider.

Regardless of the order EP attribute, sometimes my inspection overrides the default one and sometimes not.

With regards to the dynamic attributes, I tried renaming the attribute and passing it to the parent handler, but changing the attribute name causes a PSI error, so I also subclassed (delegated) XmlAttribute to have a fake name. This almost worked except that descriptor getter started to return null. I hacked that up by providing a fake descriptor, but I'm wondering why that happened.

Here's the final code:

<com.intellij.inspectionToolProvider implementation="CustomInspectionToolProvider"/>
class CustomInspectionToolProvider : InspectionToolProvider {
override
fun getInspectionClasses(): Array<Class<out LocalInspectionTool>> =
arrayOf(
CustomHtmlUnknownAttributeInspection::class.java,
)
}
class CustomHtmlUnknownAttributeInspection : HtmlUnknownAttributeInspection() {
override fun checkAttribute(attribute: XmlAttribute, holder: ProblemsHolder, isOnTheFly: Boolean) {
var modifiedAttribute: XmlAttribute? = attribute

when {
attribute.name.startsWith(':') -> // dynamic attribute; only normal/existing attributes
// prefixed with ':' are allowed
modifiedAttribute = RenamedXmlAttribute(attribute, attribute.name.drop(1))

attribute.name == "v-a" -> // static list of attributes.. in the future I might have logic
modifiedAttribute = null // that (dis)allows an attribute in combination with another
}

modifiedAttribute?.run {
super.checkAttribute(this, holder, isOnTheFly)
}
}

class RenamedXmlAttribute(
private val originalAttribute: XmlAttribute,
private val newAttributeName: String
) : XmlAttribute by originalAttribute {
override fun getName(): String =
newAttributeName

       // FIXME for some reason delegating somehow broke this
override fun getDescriptor(): XmlAttributeDescriptor =
originalAttribute.descriptor ?: AnyXmlAttributeDescriptor(newAttributeName)
}
}

 

So in short:

  1. is what I'm doing ok, or is it a terrible hack?
  2. is there a better way of doing it?
  3. how do I consistently override (or disable) the original inspection?

PS: It's the first time I use delegate ("by") functionality...looks awesome and super simple. Huge lifesaver considering the dozen or so methods of the XmlAttribute interface. :)

For reference, here's how/where HtmlUnknownAttribute is loaded: https://github.com/JetBrains/intellij-community/blob/b78b1dfbb3c86aaed5cea7f92fda711b7082fcaa/platform/platform-resources/src/META-INF/XmlPlugin.xml#L446

7 comments
Comment actions Permalink
Official comment

Hi Christian Sciberras !

The easiest way to provide custom attributes (and tags) is through Web Types. Please have a look at https://blog.jetbrains.com/webstorm/2021/09/building-a-plugin-for-webstorm-part-1/#step_implement_alpine_js_simple_directives

We are still working on a documentation for Web Types, so for now that blog post is the best place to start with. You can also check out Web Types for Angular (https://github.com/JetBrains/intellij-plugins/tree/master/AngularJS/resources/web-types) or Vue (https://github.com/JetBrains/intellij-plugins/tree/master/vuejs/resources/web-types).

Contributing Web Types should solve most of the basic problems, but if you want to bring in advanced code insight based on the source code, that's going to be available only in 2022.3 through Web Symbols API, which are coming to the platform. In 2022.2 and before it was used only by our plugins (like Vue or Angular) internally.

Let me know if you have any questions!

Comment actions Permalink

Hello Piotr Tomiak!

I just used WebTypes and I can agree, it's definitely much easier to set up. I could even skip creating a custom plugin and use WebTypes in a PhpStorm project directly (I just had to make a dummy package.json file to load them up).

I have some followup questions though:

  1. how do I define the target/source/origin of custom elements? I tried setting the source.file/offset properties with various values (./src/MyPhpClass.php, ./types.js, MyPhpClass, MyJsSymbol), but nothing worked. E.g.
    "elements": [
    {
    "name": "MyClass",
    "source": {
    "file": "./src/Components/MyClass.php",
    "offset": 42
    }
    }
    ]
  2. if I didn't misunderstand, it seems possible to define a source for an element's pattern (I think I saw this in the vue plugin, pointing to some kotlin class/provider). Is it possible to do this without a custom plugin? For me it would be best if I could specify a PHP provider class/function, but I could also provide some file with a list of patterns/symbols. If none of this is possible, I can make a PHP script that generates a WebTypes file.
  3. is it possible to provide some basic rules for the attributes? e.g. I would like to define that attribute v-as is required with v-for or that v-else-if is not allowed with v-if.
  4. to be clear for me, if I wanted to define that some attributes' value is actually an expression of some custom language, this has to be implemented in a plugin, correct? Is the vue plugin the best place to figure this out?

 

Thanks for your time!

0
Comment actions Permalink

Hi Christian!

I am glad you've liked it. Let me answer your questions.

1. We do not have any support for PHP at this moment. I am gathering feedback and trying to understand how it should be done. So, unfortunately, even a correct reference to a PHP code in Web Type source filed will not be resolved by the IDE.
2. You have two options here - either provide code analysis and contribute to appropriate symbols to Web Symbols model (Web Types is one of the ways to contribute symbols), or create a static Web Types file, from which the contributions are loaded. The first approach is great to handle cases when you need to contribute symbols from user source code, the second one works best with libraries.
3. At this moment, Web Types format does not support this case. In 2022.3 you would need to register `WebSymbolsScopeProvider` extension, which would provide `WebSymbolsScope`, which in turn would allow to filter out some symbols based on context. I plan to implement an XPath based filtering of elements through conditions in Web Types, but that will come no earlier than in 2023.1.
4. If the language is one of already supported by WebStorm/IntelliJ and is injectable, you can specify property `inject-language`. Of course the support within such expressions will be quite limited. There is plan to make it much easier to support custom expressions in attributes, but I wouldn't expect anything implemented soon. Right now you need to at least implement a parser for your custom language. It can be based on existing JavaScript (or any other language) syntax, e.g. `VueJSLanguage` or `Angular2Langauge` dialects.

0
Comment actions Permalink

Thanks for the reply Piotr Tomiak!

  1. No problem, I'll go with the answer of the 2nd question. However, FYI it also didn't work with JS symbols, but maybe I did something wrong there.
  2. I'll go with the first approach you mentioned since (a) it works on the fly and (b) I can actually link to PHP classes. I did have some remaining troubles, but that's a different topic: https://intellij-support.jetbrains.com/hc/en-us/community/posts/8114815121810
  3. Ok, I'll play around with it, maybe I could find some alternative. Another option could be to have a custom inspection.
  4. Thanks, I see that that is also used here: https://github.com/JetBrains/web-types/blob/ce8e85dd312df061c07d79b132656aa6c219c390/packages/lit/lit%402.0.0/lit.web-types.json#L83 However, I just can't get it to work. I tried various variations (JavaScript, PHP, RegExp) and even copying that attribute as is. The attribute shows up in the autocompletion suggestions, but the value isn't syntax-highlighted at all. Additionally, that property is absent from the web-types schema. I'm target PS 2022.2.3.
0
Comment actions Permalink

Correction for point 4:

I see that injection does work, but only in standalone HTML or PHP(outside PHP tags) files.

Is this something I can somehow fix? 😅

0
Comment actions Permalink

Christian Sciberras Unfortunately injection within injection is not supported by the platform. At some point we will be able to integrate the parser with web-types, but there still some way to go.

0

Please sign in to leave a comment.