Customising/extending behaviour of HtmlUnknownAttributeInspection Follow
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:
- the entries added are permanently stored in the inspection's settings - I don't rather not have the user see this as a customisation
- 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:
- is what I'm doing ok, or is it a terrible hack?
- is there a better way of doing it?
- 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
Please sign in to leave a 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!
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:
Thanks for your time!
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.
Thanks for the reply Piotr Tomiak!
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? 😅
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.
ok, thanks anyway