IntelliJ Platform Plugin SDK Help

Implementing Web Symbols

The core element of the framework is a WebSymbol, representing an entity in the Web Symbols model. This symbol is characterized by namespace, kind and name properties, with its lifecycle encapsulated within a single read action. To ensure its survival between read actions, use WebSymbol.createPointer() to create a symbol pointer. Provided the symbol remains valid, dereferencing the pointer will return a new instance of the symbol. It should be noted that during a write action, the symbol might not survive a PSI tree commit. Therefore, creating a pointer prior to the commit and dereferencing it post-commit is advised.

The property namespace describes which language or concept (not tied to a particular language) the symbol belongs to, and kind describes which group of symbols within that particular language or concept it belongs to. Examples:

  • a CSS property: namespace: CSS, kind: properties

  • a Java class: namespace: Java, kind: classes

  • a plugin extension: namespace: ij-plugin, kind: extensions

A Web Symbol can originate from source code analysis, or it can be a symbol statically defined through Web Types (JSON) or some other custom format. In both cases, such a symbol can have some source defined. Each symbol is treated by the framework the same, regardless of their origin.

General Properties

WebSymbol has a number of properties which are used across IDE features:

namespace

Describes which language or concept the symbol belongs to.

kind

Describes which group of symbols within the particular language or concept (namespace) the symbol belongs to. The kind should be plural in form, e.g. "attributes".

name

The name of the symbol. If the symbol does not have a pattern, the name will be used as-is for matching.

origin

Specifies where this symbol comes from. Besides descriptive information like framework, library, version, or default icon, it also provides an interface to load symbol types and icons.

icon

An optional icon associated with the symbol, which is going to be used across the IDE. If none is specified, a default icon of the origin will be used and if that’s not available, a default icon for symbol namespace and kind.

priority

Symbols with higher priority will have precedence over those with lower priority, when matching is performed. Symbols with higher priority will also show higher on the completion list.

proximity

Provides an additional way to sort symbols in the code completion list within a particular priority. The value must be a non-negative integer, and the higher proximity, the higher the symbol would be on the list.

apiStatus

Since 2023.2 - replaces deprecated and experimental properties

Documents API status of the symbol. It is one of the sub-interfaces of WebSymbolApiStatus: Stable, Experimental or Deprecated. Deprecated symbols are appropriately highlighted in the code editor, code completion, and quick documentation.

deprecated

Removed in 2023.2 - replaced with apiStatus property

Documents, whether the symbol is deprecated. Deprecated symbols are appropriately highlighted in the code editor, code completion, and quick documentation.

experimental

Removed in 2023.2 - replaced with apiStatus property

Documents, whether the symbol is considered an experimental feature and should be used with caution and might be removed or its API altered in the future.

required

Whether this symbol is required. What "is required" means depends on the symbol. For instance, for an HTML attribute, it would mean that the attribute is required to be present for the particular HTML element. For JavaScript property, it would mean that it is not optional, so it cannot be undefined.

defaultValue

If the symbol represents some property, variable, or anything that can hold a value, this property documents what is the default value.

type

The type of the symbol. The type can be interpreted only within the context of symbol origin and in regard to its namespace and kind. The type may be a language type, coming from e.g., Java or TypeScript, or it may be any arbitrary value. Usually a type would be associated with symbols, which can hold a value, or represent some language symbol, like class, method, etc.

psiContext

A PsiElement, which is a file or an element, which can be used to roughly locate the source of the symbol within a project to provide a context for loading additional information, like types. If the symbol is PsiSourcedWebSymbol (see PsiSourcedWebSymbol), then psiContext is equal to source.

properties

Various symbol properties. There should be no assumption on the type of properties. Properties can be used by plugins to provide additional information on the symbol. See Web Types Special Properties section for reference to the custom properties supported by IDEs.

presentation

Returns TargetPresentation used by SearchTarget and RenameTarget. Default implementations of WebSymbolRenameTarget and WebSymbolSearchTarget use the presentation property.

Documentation Properties

The following properties handle generation of Quick Doc in the IDE:

description

An optional text, which describes the symbol's purpose and usage. It is rendered in the documentation popup or view.

descriptionSections

Additional sections, to be rendered in the symbols’ documentation. Each section should have a name, but the contents are optional.

docUrl

An optional URL to a website with detailed symbol's documentation

documentation

Removed in 2023.1.1 - replaced by createDocumentation()

An interface holding information required to render documentation for the symbol. To customize symbols documentation, one can override the method, or implementWebSymbolDocumentationCustomizer. WebSymbolDocumentation interface provides builder methods for customizing the documentation. with* methods return a copy of the documentation with customized fields.

The following properties are related to name matching and code completion queries:

pattern

The pattern to match names against. As a result of pattern matching, a WebSymbolMatch will be created. A pattern may specify that a reference to other Web Symbols is expected in some part of it. For such places, appropriate segments with referenced Web Symbols will be created and navigation, validation, and refactoring support are available out-of-the-box.

queryScope

When a pattern is being evaluated, matched symbols can provide additional scope for further resolution in the pattern. By default, the queryScope returns the symbol itself

virtual

Some symbols represent only a framework syntax, which does not translate to a particular symbol in the runtime. For instance, a Vue directive, which needs to be prefixed with v- will result in some special code generated, but as such is not a real HTML attribute. This distinction allows us to ignore such symbols when looking for references.

abstract

Some symbols may have a lot in common with each other, and one can use abstract symbols as their super symbol. For performance reasons, only statically defined symbols (Web Types, Custom Elements Manifest) can inherit from other statically defined symbols. For dynamically defined symbols, regular class inheritance should be used.

extension

Specifies whether the symbol is an extension. When matched along with a non-extension symbol, it can provide or override some properties of the symbol, or it can extend its scope contents.

HTML support

attributeValue

A special property to support symbols representing HTML attributes. It can specify the kind (plain, expression, no-value), type (boolean, number, string, enum, complex, of-match), whether an attribute value is required, a default value, and the result type of value expression in the appropriate language. If COMPLEX type is set, the value of langType will be used and if OF_MATCH, the type of the symbol will be used. When merging information from several segments in the WebSymbolMatch, first non-null property values take precedence. By default - when properties are null - attribute value is of plain type and is required.

Methods

createPointer()

Returns the pointer to the symbol, which can survive between read actions. The dereferenced symbol should be valid, e.g., any PSI-based properties should return valid PsiElements.

getModificationCount()

Symbols can be used in CachedValue s as dependencies. If a symbol instance can mutate over time, it should properly implement this method.

isEquivalentTo()

Returns true if two symbols are the same or equivalent for resolve purposes.

adjustNameForRefactoring()

Web Symbols can have various naming conventions. This method is used by the framework to determine a new name for a symbol based on its occurrence.

getDocumentationTarget()

Since 2023.1.1

Used by the Web Symbols framework to get a DocumentationTarget, which handles documentation rendering for the symbol. The default implementation will use createDocumentation() to render the documentation.

createDocumentation()

Since 2023.1.1 - replaces documentation property

Returns WebSymbolDocumentation - an interface holding information required to render documentation for the symbol. By default, its contents are built from the available Web Symbol information. To customize symbols documentation, one can override the method, or implement WebSymbolDocumentationCustomizer.

WebSymbolDocumentation interface provides builder methods for customizing the documentation. with* methods return a copy of the documentation with customized fields.

PsiSourcedWebSymbol

A symbol should implement PsiSourcedWebSymbol if its declaration is a regular PsiElement, e.g., a variable or a declared type. Once a symbol implements this interface, it can be searched and refactored together with the PSI element declaration. In case a symbol is part of a PsiElement (for instance, being part of a string literal), spans multiple PSI elements, or does not correlate one-to-one with a PSI element, contribution of a dedicated declaration provider instead of implementing this interface is recommended.

Properties

source

The PsiElement, which is the symbol declaration.

CompositeWebSymbol

WebSymbolMatch and some special symbols can have a name, which consists of other Web Symbols.

Properties

nameSegments

List of WebSymbolNameSegment. Each segment describes a range in the symbol name. Segments can be built of other Web Symbols and/or have related matching problems - missing required part, unknown symbol name or be a duplicate of another segment. See the Model Queries Example section for an example.

Web Symbols Scope

Web Symbols are contained within a loose model built from Web Symbols scopes, each time anew for a particular context. Each Web Symbol is also a WebSymbolsScope and it can contain other Web Symbols. For instance, an HTML element symbol would contain some HTML attributes symbols, or a JavaScript class symbol would contain field and method symbols. When configuring queries, Web Symbols scopes are added to the list to create an initial scope for symbols resolve.

Methods

getSymbols()

Returns symbols within the scope. If provided name is null, no pattern evaluation will happen and all symbols of a particular kind and from a particular namespace will be returned.

getCodeCompletions()

Returns code completions for symbols within the scope.

isExclusiveFor()

When scope is exclusive for a particular namespace and kind, resolve will not continue down the stack during pattern matching.

createPointer()

Returns the pointer to the symbol scope, which can survive between read actions. The dereferenced symbol scope should be valid.

getModificationCount()

Symbol scopes are used in CachedValues as dependencies for query executors. If a symbol scope instance can mutate over time, it should properly implement this method.

When implementing a scope containing many elements, an extension of WebSymbolsScopeWithCache is advised. This structure caches the list of symbols and uses an efficient cache mechanism to speed up queries. On extension of this class, it's only necessary to override initialize() and provide parameters to the super constructor to specify the caching strategy for the results.

Model Queries

Web Symbols can contain patterns, which allow to compose them from other Web Symbols. To find which symbols match available patterns, we need to make a match query. One can also run a code completion query, which will produce a list of valid completions in the provided context.

To perform a query, create a WebSymbolsQueryExecutor using WebSymbolsQueryExecutorFactory. The query executor will be configured by all the registered WebSymbolsQueryConfigurator 's based on the provided PSI context. Configurators will provide initial Web Symbol scopes, rules for calculating Web Symbols context, and rules for symbol names conversion.

The result of the match query is a list of WebSymbols. Some of them might be WebSymbolMatch es. Such objects represent complex matches when patterns are used. Web Symbol Match has nameSegments property, which precisely describes how segments of the name relate to referenced Web Symbols and whether there are any problems with resolution or the name itself.

When working with code completion, one can query for the list of code completions. To properly calculate completions, a position in the current text under completion is required. As a result, a list of WebSymbolCodeCompletionItem will be provided.

Example

Let’s take a Vue directive as an example. It is a special HTML attribute processed by the Vue framework in runtime or during compile, which results in additional code being attached to the DOM element. Its structure looks as follows:

JavaScript Example Image

An example of how Vue directive might be declared in Web Types is here. Once a match query is run on v-on:click.once.alt, we will get a WebSymbolMatch with the following segments:

  1. v-: Vue directive pattern symbol

  2. on: Vue on directive

  3. :

  4. click: DOM click event symbol

  5. .

  6. once: Vue on directive once modifier

  7. alt: Vue on directive alt modifier

Patterns

Usually one would create such elements using Web Types, but sometimes there might be a need to do that programmatically.

To simplify resolution and make it less ambiguous, a segment to match is selected by taking everything up to static prefixes of the following patterns. Thus, if we want to have symbol references and regular expressions in the pattern, they either have to terminate the pattern or must be followed by a static text. A regular pattern static prefix is also considered a static text.

There are seven types of patterns:

  1. String match: try to match an exact text, the match is case-sensitive

  2. Regular expression match: try to match a regular expression, the match can be case-insensitive

  3. Symbol reference placeholder: a symbol reference resolve will be attempted when this pattern is reached. A resolve will be made by the symbols provider from an enclosing complex pattern. If none of the symbols match the segment, the segment will have UNKNOWN_SYMBOL problem reported. The matched symbol might be a WebSymbolMatch itself, which allows for nesting patterns.

  4. Pattern sequence: a sequence of patterns. If some patterns are not matched, an empty segment with MISSING_REQUIRED_PART will be created.

  5. Complex pattern: this pattern is called complex, because it makes several things:

    1. The provided patterns are treated as alternatives.

    2. It can have symbols resolver, which is used by nested symbol reference placeholder patterns.

    3. It allows adding an additional scope to resolve stack

    4. A complex pattern might be optional, in which case its absence is not reported as an error in an enclosing sequence or complex pattern

    5. The match can be repeated, and any duplicate segments might have DUPLICATED problem set

    6. It can override proximity and priority, which by default is based on priority and proximity of matched symbols.

  6. Completion auto popup: a special pattern, which works only in code completion queries. It delimits the place where when creating code completion items, pattern evaluation should be stopped and ... added. Selecting such items will result in adding the prefix part, and then another code completion popup will open. The pattern can be sticky, which means that the prefix will be shown in the nested code completion list.

  7. Single symbol reference (since 2023.2): try to match text against the symbol name, but put a reference to another element.

Query Context

When performing queries, some symbols should be excluded and others included in particular contexts. For instance, if we have an Angular project, none of the Vue components should be available. WebSymbolsContext is created using rules provided by WebSymbolsQueryConfigurators with the addition of custom WebSymbolsContextProvider. As a result, for each kind of context, there is at most a single name assigned. WebSymbolsContext can also be used outside the WebSymbolsQueryExecutor as an efficient way to determine whether to enable or disable particular functionality in the IDE based on PSI or VFS context.

Query stack

The stack is used as a scope for resolving symbols. All scopes provided by WebSymbolsQueryConfigurator s together with the list of additional scopes passed as arguments to the query create an initial query stack. Each time a symbol is matched, the list returned by queryScope property is added to the stack for any subsequent matches further right the pattern.

Declarations, References, Search, Refactoring

To provide locations of declarations of Web Symbols, which are not PsiSourcedWebSymbol s, a dedicated WebSymbolDeclarationProvider should be registered. It should return a list of WebSymbolDeclaration s in a particular PsiElement at a particular offset.

Similarly, to provide references, a PsiSymbolReferenceProvider should be registered. It should return WebSymbolReference objects from PsiSymbolReferenceProvider.getReferences().

To support search/finding usages, Web Symbol needs to implement SearchTarget or a WebSymbolSearchTarget needs to be provided for it through a SymbolSearchTargetFactory.

To support name refactoring, the RenameTarget interface needs to be implemented, or a WebSymbolRenameTarget needs to be provided for it through a SymbolRenameTargetFactory.

Last modified: 08 April 2024