use std::collections::HashSet;
use ink_analyzer_ir::ast::{HasAttrs, HasDocComments, HasModuleItem, HasName};
use ink_analyzer_ir::syntax::{
AstNode, AstToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
};
use ink_analyzer_ir::{
ast, ChainExtension, Contract, Extension, Function, HasInkImplParent, InkArg, InkArgKind,
InkArgValueKind, InkAttribute, InkAttributeKind, InkEntity, InkImpl, InkMacroKind,
IsInkCallable, IsInkStruct, IsInkTrait, IsIntId, Message, MinorVersion, Selector, Storage,
TraitDefinition,
};
use itertools::Itertools;
use once_cell::sync::Lazy;
use regex::Regex;
use crate::{resolution, utils, Version};
pub fn valid_sibling_ink_args(attr_kind: InkAttributeKind, version: Version) -> Vec<InkArgKind> {
match attr_kind {
InkAttributeKind::Macro(macro_kind) => {
match macro_kind {
InkMacroKind::ChainExtension if version.is_v5() => {
vec![InkArgKind::Extension]
}
InkMacroKind::ChainExtension => Vec::new(),
InkMacroKind::Contract => vec![InkArgKind::Env, InkArgKind::KeepAttr],
InkMacroKind::ContractRef if version.is_gte_v6() => {
vec![InkArgKind::Abi, InkArgKind::Env]
}
InkMacroKind::Error if version.is_gte_v6() => Vec::new(),
InkMacroKind::Event if version.is_v5() => {
vec![InkArgKind::Anonymous, InkArgKind::SignatureTopic]
}
InkMacroKind::Event if version.is_gte_v6() => {
vec![
InkArgKind::Anonymous,
InkArgKind::Name,
InkArgKind::SignatureTopic,
]
}
InkMacroKind::StorageItem if version.is_lte_v5() => vec![InkArgKind::Derive],
InkMacroKind::StorageItem => vec![InkArgKind::Packed, InkArgKind::Derive],
InkMacroKind::Test => Vec::new(),
InkMacroKind::TraitDefinition => vec![InkArgKind::KeepAttr, InkArgKind::Namespace],
InkMacroKind::E2ETest if version.is_legacy() => vec![
InkArgKind::AdditionalContracts,
InkArgKind::Environment,
InkArgKind::KeepAttr,
],
InkMacroKind::E2ETest => {
vec![InkArgKind::Backend, InkArgKind::Environment]
}
InkMacroKind::ScaleDerive if version.is_gte_v5() => {
vec![InkArgKind::Encode, InkArgKind::Decode, InkArgKind::TypeInfo]
}
_ => Vec::new(),
}
}
InkAttributeKind::Arg(arg_kind) => {
match arg_kind {
InkArgKind::Storage => Vec::new(),
InkArgKind::Event if version.is_legacy() => vec![InkArgKind::Anonymous],
InkArgKind::Event if version.is_v5() => {
vec![InkArgKind::Anonymous, InkArgKind::SignatureTopic]
}
InkArgKind::Event => {
vec![
InkArgKind::Anonymous,
InkArgKind::Name,
InkArgKind::SignatureTopic,
]
}
InkArgKind::SignatureTopic if version.is_v5() => {
vec![InkArgKind::Event]
}
InkArgKind::SignatureTopic if version.is_gte_v6() => {
vec![InkArgKind::Event, InkArgKind::Name]
}
InkArgKind::Anonymous if version.is_lte_v5() => {
vec![InkArgKind::Event]
}
InkArgKind::Anonymous => vec![InkArgKind::Event, InkArgKind::Name],
InkArgKind::Topic => Vec::new(),
InkArgKind::Impl => vec![InkArgKind::Namespace],
InkArgKind::Constructor if version.is_lte_v5() => vec![
InkArgKind::Default,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkArgKind::Constructor => vec![
InkArgKind::Default,
InkArgKind::Name,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkArgKind::Message if version.is_lte_v5() => vec![
InkArgKind::Default,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkArgKind::Message => vec![
InkArgKind::Default,
InkArgKind::Name,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkArgKind::Env if version.is_lte_v5() => vec![InkArgKind::KeepAttr],
InkArgKind::Env => vec![InkArgKind::Abi, InkArgKind::KeepAttr],
InkArgKind::Abi if version.is_gte_v6() => vec![InkArgKind::Env],
InkArgKind::Extension if version.is_legacy() => vec![InkArgKind::HandleStatus],
InkArgKind::Extension => Vec::new(),
InkArgKind::Function if version.is_v5() => {
vec![InkArgKind::HandleStatus]
}
InkArgKind::Function => Vec::new(),
InkArgKind::Derive if version.is_lte_v5() => Vec::new(),
InkArgKind::Derive => vec![InkArgKind::Packed],
InkArgKind::KeepAttr => vec![InkArgKind::Env, InkArgKind::Namespace],
InkArgKind::Name if version.is_gte_v6() => vec![
InkArgKind::Constructor,
InkArgKind::Event,
InkArgKind::Message,
],
InkArgKind::Namespace => vec![InkArgKind::KeepAttr, InkArgKind::Impl],
InkArgKind::HandleStatus if version.is_legacy() => vec![InkArgKind::Extension],
InkArgKind::HandleStatus if version.is_v5() => vec![InkArgKind::Function],
InkArgKind::HandleStatus => Vec::new(),
InkArgKind::Packed if version.is_gte_v6() => vec![InkArgKind::Derive],
InkArgKind::Payable if version.is_lte_v5() => vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Selector,
],
InkArgKind::Payable => vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Name,
InkArgKind::Message,
InkArgKind::Selector,
],
InkArgKind::Default if version.is_lte_v5() => vec![
InkArgKind::Constructor,
InkArgKind::Message,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkArgKind::Default => vec![
InkArgKind::Constructor,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkArgKind::Selector if version.is_lte_v5() => vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Payable,
],
InkArgKind::Selector => vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Payable,
],
_ => Vec::new(),
}
}
}
}
pub fn valid_quasi_direct_descendant_ink_args(
attr_kind: InkAttributeKind,
version: Version,
) -> Vec<InkArgKind> {
match attr_kind {
InkAttributeKind::Macro(macro_kind) => {
match macro_kind {
InkMacroKind::ChainExtension if version.is_legacy() => {
vec![InkArgKind::Extension, InkArgKind::HandleStatus]
}
InkMacroKind::ChainExtension if version.is_v5() => {
vec![InkArgKind::Function, InkArgKind::HandleStatus]
}
InkMacroKind::ChainExtension => Vec::new(),
InkMacroKind::Contract if version.is_legacy() => vec![
InkArgKind::Anonymous,
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Event,
InkArgKind::Impl,
InkArgKind::Message,
InkArgKind::Namespace,
InkArgKind::Payable,
InkArgKind::Selector,
InkArgKind::Storage,
],
InkMacroKind::Contract if version.is_v5() => vec![
InkArgKind::Anonymous,
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Event,
InkArgKind::Impl,
InkArgKind::Message,
InkArgKind::Namespace,
InkArgKind::Payable,
InkArgKind::Selector,
InkArgKind::SignatureTopic,
InkArgKind::Storage,
],
InkMacroKind::Contract => vec![
InkArgKind::Anonymous,
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Event,
InkArgKind::Impl,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Namespace,
InkArgKind::Payable,
InkArgKind::Selector,
InkArgKind::SignatureTopic,
InkArgKind::Storage,
],
InkMacroKind::ContractRef if version.is_gte_v6() => vec![
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkMacroKind::Error if version.is_gte_v6() => Vec::new(),
InkMacroKind::Event if version.is_gte_v5() => {
vec![InkArgKind::Topic]
}
InkMacroKind::TraitDefinition if version.is_lte_v5() => vec![
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkMacroKind::TraitDefinition => vec![
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Payable,
InkArgKind::Selector,
],
_ => Vec::new(),
}
}
InkAttributeKind::Arg(arg_kind) => {
match arg_kind {
InkArgKind::Event | InkArgKind::Anonymous => vec![InkArgKind::Topic],
InkArgKind::SignatureTopic if version.is_gte_v5() => {
vec![InkArgKind::Topic]
}
InkArgKind::Topic => Vec::new(),
InkArgKind::Env | InkArgKind::KeepAttr if version.is_lte_v5() => {
vec![
InkArgKind::Anonymous,
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Event,
InkArgKind::Impl,
InkArgKind::Message,
InkArgKind::Namespace,
InkArgKind::Payable,
InkArgKind::Selector,
InkArgKind::Storage,
]
}
InkArgKind::Env | InkArgKind::KeepAttr => vec![
InkArgKind::Anonymous,
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Event,
InkArgKind::Impl,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Namespace,
InkArgKind::Payable,
InkArgKind::Selector,
InkArgKind::Storage,
],
InkArgKind::Abi if version.is_gte_v6() => vec![
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkArgKind::Impl | InkArgKind::Namespace if version.is_lte_v5() => {
vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Payable,
InkArgKind::Selector,
]
}
InkArgKind::Impl | InkArgKind::Namespace => vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Payable,
InkArgKind::Selector,
],
InkArgKind::Name if version.is_gte_v6() => vec![InkArgKind::Topic],
_ => Vec::new(),
}
}
}
}
pub fn valid_quasi_direct_descendant_ink_macros(
attr_kind: InkAttributeKind,
version: Version,
) -> Vec<InkMacroKind> {
match attr_kind {
InkAttributeKind::Macro(macro_kind) => {
match macro_kind {
InkMacroKind::Contract if version.is_legacy() => {
vec![
InkMacroKind::ChainExtension,
InkMacroKind::StorageItem,
InkMacroKind::Test,
InkMacroKind::TraitDefinition,
InkMacroKind::E2ETest,
]
}
InkMacroKind::Contract if version.is_v5() => {
vec![
InkMacroKind::ChainExtension,
InkMacroKind::Event,
InkMacroKind::ScaleDerive,
InkMacroKind::StorageItem,
InkMacroKind::Test,
InkMacroKind::TraitDefinition,
InkMacroKind::E2ETest,
]
}
InkMacroKind::Contract => {
vec![
InkMacroKind::ContractRef,
InkMacroKind::Error,
InkMacroKind::Event,
InkMacroKind::ScaleDerive,
InkMacroKind::StorageItem,
InkMacroKind::Test,
InkMacroKind::TraitDefinition,
InkMacroKind::E2ETest,
]
}
InkMacroKind::ChainExtension if version.is_gte_v6() => Vec::new(),
InkMacroKind::ChainExtension
| InkMacroKind::TraitDefinition
| InkMacroKind::Test
| InkMacroKind::E2ETest
if version.is_gte_v5() =>
{
vec![InkMacroKind::ScaleDerive]
}
_ => Vec::new(),
}
}
InkAttributeKind::Arg(arg_kind) => {
match arg_kind {
InkArgKind::Storage
| InkArgKind::Event
| InkArgKind::Anonymous
| InkArgKind::SignatureTopic
if version.is_gte_v5() =>
{
Vec::new()
}
_ if version.is_gte_v5() => {
vec![InkMacroKind::ScaleDerive]
}
_ => Vec::new(),
}
}
}
}
pub fn valid_ink_args_by_syntax_kind(syntax_kind: SyntaxKind, version: Version) -> Vec<InkArgKind> {
match syntax_kind {
SyntaxKind::MODULE | SyntaxKind::MOD_KW => Vec::new(),
SyntaxKind::TRAIT | SyntaxKind::TRAIT_KW => Vec::new(),
SyntaxKind::STRUCT | SyntaxKind::STRUCT_KW if version.is_legacy() => vec![
InkArgKind::Anonymous,
InkArgKind::Event,
InkArgKind::Storage,
],
SyntaxKind::STRUCT | SyntaxKind::STRUCT_KW if version.is_v5() => vec![
InkArgKind::Anonymous,
InkArgKind::Event,
InkArgKind::SignatureTopic,
InkArgKind::Storage,
],
SyntaxKind::STRUCT | SyntaxKind::STRUCT_KW => vec![
InkArgKind::Anonymous,
InkArgKind::Event,
InkArgKind::Name,
InkArgKind::SignatureTopic,
InkArgKind::Storage,
],
SyntaxKind::ENUM | SyntaxKind::ENUM_KW | SyntaxKind::UNION | SyntaxKind::UNION_KW => {
Vec::new()
}
SyntaxKind::RECORD_FIELD => vec![InkArgKind::Topic],
SyntaxKind::FN | SyntaxKind::FN_KW if version.is_legacy() => vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Extension,
InkArgKind::HandleStatus,
InkArgKind::Message,
InkArgKind::Payable,
InkArgKind::Selector,
],
SyntaxKind::FN | SyntaxKind::FN_KW if version.is_v5() => vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Function,
InkArgKind::HandleStatus,
InkArgKind::Message,
InkArgKind::Payable,
InkArgKind::Selector,
],
SyntaxKind::FN | SyntaxKind::FN_KW => vec![
InkArgKind::Constructor,
InkArgKind::Default,
InkArgKind::Message,
InkArgKind::Name,
InkArgKind::Payable,
InkArgKind::Selector,
],
SyntaxKind::IMPL | SyntaxKind::IMPL_KW => vec![InkArgKind::Impl, InkArgKind::Namespace],
_ => Vec::new(),
}
}
pub fn valid_ink_macros_by_syntax_kind(
syntax_kind: SyntaxKind,
version: Version,
) -> Vec<InkMacroKind> {
match syntax_kind {
SyntaxKind::MODULE | SyntaxKind::MOD_KW => vec![InkMacroKind::Contract],
SyntaxKind::TRAIT | SyntaxKind::TRAIT_KW if version.is_lte_v5() => {
vec![InkMacroKind::ChainExtension, InkMacroKind::TraitDefinition]
}
SyntaxKind::TRAIT | SyntaxKind::TRAIT_KW => {
vec![InkMacroKind::ContractRef, InkMacroKind::TraitDefinition]
}
SyntaxKind::FN | SyntaxKind::FN_KW => vec![InkMacroKind::Test, InkMacroKind::E2ETest],
SyntaxKind::STRUCT | SyntaxKind::STRUCT_KW if version.is_legacy() => {
vec![InkMacroKind::StorageItem]
}
SyntaxKind::STRUCT | SyntaxKind::STRUCT_KW if version.is_v5() => vec![
InkMacroKind::Event,
InkMacroKind::ScaleDerive,
InkMacroKind::StorageItem,
],
SyntaxKind::STRUCT | SyntaxKind::STRUCT_KW => vec![
InkMacroKind::Event,
InkMacroKind::Error,
InkMacroKind::ScaleDerive,
InkMacroKind::StorageItem,
],
SyntaxKind::ENUM | SyntaxKind::ENUM_KW | SyntaxKind::UNION | SyntaxKind::UNION_KW
if version.is_legacy() =>
{
vec![InkMacroKind::StorageItem]
}
SyntaxKind::ENUM | SyntaxKind::ENUM_KW | SyntaxKind::UNION | SyntaxKind::UNION_KW
if version.is_v5() =>
{
vec![InkMacroKind::ScaleDerive, InkMacroKind::StorageItem]
}
SyntaxKind::ENUM | SyntaxKind::ENUM_KW | SyntaxKind::UNION | SyntaxKind::UNION_KW => vec![
InkMacroKind::Error,
InkMacroKind::ScaleDerive,
InkMacroKind::StorageItem,
],
_ => Vec::new(),
}
}
pub fn primary_ink_attribute_candidate(
mut attrs: impl Iterator<Item = InkAttribute>,
) -> Option<(InkAttribute, bool)> {
attrs.next().and_then(|first_attr| {
let first_attr_range = first_attr.syntax().text_range();
[first_attr]
.into_iter()
.chain(attrs)
.filter(|attr| {
!matches!(
attr.kind(),
InkAttributeKind::Macro(InkMacroKind::Unknown)
| InkAttributeKind::Arg(InkArgKind::Unknown)
)
})
.sorted()
.next()
.map(|primary_candidate| {
let is_first = first_attr_range == primary_candidate.syntax().text_range();
(primary_candidate, is_first)
})
})
}
pub fn primary_ink_attribute_kind_suggestions(
attr_kind: InkAttributeKind,
version: Version,
) -> Vec<InkAttributeKind> {
match attr_kind {
InkAttributeKind::Arg(arg_kind) => {
match arg_kind {
InkArgKind::AdditionalContracts if version.is_legacy() => {
vec![InkAttributeKind::Macro(InkMacroKind::E2ETest)]
}
InkArgKind::AdditionalContracts => Vec::new(),
InkArgKind::Anonymous if version.is_legacy() => {
vec![InkAttributeKind::Arg(InkArgKind::Event)]
}
InkArgKind::Anonymous => {
vec![
InkAttributeKind::Macro(InkMacroKind::Event),
InkAttributeKind::Arg(InkArgKind::Event),
]
}
InkArgKind::Backend if version.is_gte_v5() => {
vec![InkAttributeKind::Macro(InkMacroKind::E2ETest)]
}
InkArgKind::Decode | InkArgKind::Encode | InkArgKind::TypeInfo
if version.is_gte_v5() =>
{
vec![InkAttributeKind::Macro(InkMacroKind::ScaleDerive)]
}
InkArgKind::Default | InkArgKind::Payable | InkArgKind::Selector => vec![
InkAttributeKind::Arg(InkArgKind::Constructor),
InkAttributeKind::Arg(InkArgKind::Message),
],
InkArgKind::Environment => {
vec![InkAttributeKind::Macro(InkMacroKind::E2ETest)]
}
InkArgKind::KeepAttr => vec![
InkAttributeKind::Macro(InkMacroKind::Contract),
InkAttributeKind::Macro(InkMacroKind::TraitDefinition),
InkAttributeKind::Macro(InkMacroKind::E2ETest),
],
InkArgKind::HandleStatus if version.is_legacy() => {
vec![InkAttributeKind::Arg(InkArgKind::Extension)]
}
InkArgKind::HandleStatus if version.is_v5() => {
vec![InkAttributeKind::Arg(InkArgKind::Function)]
}
InkArgKind::HandleStatus => Vec::new(),
InkArgKind::Namespace => vec![
InkAttributeKind::Macro(InkMacroKind::TraitDefinition),
InkAttributeKind::Arg(InkArgKind::Impl),
],
InkArgKind::Name if version.is_gte_v6() => vec![
InkAttributeKind::Arg(InkArgKind::Constructor),
InkAttributeKind::Macro(InkMacroKind::Event),
InkAttributeKind::Arg(InkArgKind::Event),
InkAttributeKind::Arg(InkArgKind::Message),
],
InkArgKind::SignatureTopic if version.is_gte_v5() => {
vec![
InkAttributeKind::Macro(InkMacroKind::Event),
InkAttributeKind::Arg(InkArgKind::Event),
]
}
_ => Vec::new(),
}
}
InkAttributeKind::Macro(_) => Vec::new(),
}
}
pub fn remove_duplicate_ink_arg_suggestions(
suggestions: &mut Vec<InkArgKind>,
attr_parent: &SyntaxNode,
) {
let already_annotated_ink_args: Vec<InkArgKind> = ink_analyzer_ir::ink_attrs(attr_parent)
.flat_map(|ink_attr| ink_attr.args().to_owned())
.map(|ink_arg| *ink_arg.kind())
.collect();
suggestions.retain(|arg_kind| !already_annotated_ink_args.contains(arg_kind));
}
pub fn remove_duplicate_ink_macro_suggestions(
suggestions: &mut Vec<InkMacroKind>,
attr_parent: &SyntaxNode,
) {
let already_annotated_ink_macros: Vec<InkMacroKind> = ink_analyzer_ir::ink_attrs(attr_parent)
.filter_map(|ink_attr| match ink_attr.kind() {
InkAttributeKind::Macro(macro_kind) => Some(*macro_kind),
InkAttributeKind::Arg(_) => None,
})
.collect();
suggestions.retain(|arg_kind| !already_annotated_ink_macros.contains(arg_kind));
}
pub fn remove_conflicting_ink_arg_suggestions(
suggestions: &mut Vec<InkArgKind>,
attr_parent: &SyntaxNode,
version: Version,
) {
if let Some((primary_ink_attr, ..)) =
primary_ink_attribute_candidate(ink_analyzer_ir::ink_attrs(attr_parent))
{
let attr_kind = primary_ink_attr.kind();
let valid_siblings = valid_sibling_ink_args(*attr_kind, version);
suggestions.retain(|arg_kind| valid_siblings.contains(arg_kind));
if version.is_gte_v5()
&& matches!(
attr_kind,
InkAttributeKind::Macro(InkMacroKind::Event)
| InkAttributeKind::Arg(
InkArgKind::Event | InkArgKind::Anonymous | InkArgKind::SignatureTopic
)
)
{
let is_anonymous = *attr_kind == InkAttributeKind::Arg(InkArgKind::Anonymous)
|| ink_analyzer_ir::ink_arg_by_kind(attr_parent, InkArgKind::Anonymous).is_some();
let has_signature = *attr_kind == InkAttributeKind::Arg(InkArgKind::SignatureTopic)
|| ink_analyzer_ir::ink_arg_by_kind(attr_parent, InkArgKind::SignatureTopic)
.is_some();
if is_anonymous || has_signature {
suggestions.retain(|arg_kind| {
(!is_anonymous || *arg_kind != InkArgKind::SignatureTopic)
&& (!has_signature || *arg_kind != InkArgKind::Anonymous)
});
}
}
}
}
pub fn remove_invalid_ink_arg_suggestions_for_parent_item(
suggestions: &mut Vec<InkArgKind>,
attr_parent: &SyntaxNode,
) {
if ast::Impl::can_cast(attr_parent.kind()) {
let impl_item =
ast::Impl::cast(attr_parent.clone()).expect("Should be able to cast to `impl` item.");
if impl_item.trait_().is_some() {
suggestions.retain(|arg_kind| *arg_kind != InkArgKind::Namespace);
}
}
}
pub fn remove_invalid_ink_arg_suggestions_for_parent_ink_scope(
suggestions: &mut Vec<InkArgKind>,
attr_parent: &SyntaxNode,
version: Version,
) {
let mut has_chain_extension_parent = false;
let parent_ink_scope_valid_ink_args: Vec<InkArgKind> =
ink_analyzer_ir::ink_attrs_closest_ancestors(attr_parent)
.flat_map(|attr| {
if *attr.kind() == InkAttributeKind::Macro(InkMacroKind::ChainExtension) {
has_chain_extension_parent = true;
}
valid_quasi_direct_descendant_ink_args(*attr.kind(), version)
})
.collect();
if version.is_gte_v6()
&& has_chain_extension_parent
&& parent_ink_scope_valid_ink_args.is_empty()
{
*suggestions = Vec::new();
return;
}
if !parent_ink_scope_valid_ink_args.is_empty() {
suggestions.retain(|arg_kind| {
parent_ink_scope_valid_ink_args.is_empty()
|| parent_ink_scope_valid_ink_args.contains(arg_kind)
});
}
}
pub fn remove_duplicate_conflicting_and_invalid_scope_ink_arg_suggestions(
suggestions: &mut Vec<InkArgKind>,
ink_attr: &InkAttribute,
version: Version,
) {
if let Some(attr_parent) = ink_attr.syntax().parent() {
remove_duplicate_ink_arg_suggestions(suggestions, &attr_parent);
remove_conflicting_ink_arg_suggestions(suggestions, &attr_parent, version);
if let InkAttributeKind::Arg(_) = ink_attr.kind() {
remove_invalid_ink_arg_suggestions_for_parent_ink_scope(
suggestions,
&attr_parent,
version,
);
}
}
}
pub fn remove_invalid_ink_macro_suggestions_for_parent_ink_scope(
suggestions: &mut Vec<InkMacroKind>,
attr_parent: &SyntaxNode,
version: Version,
) {
let mut ink_ancestors = ink_analyzer_ir::ink_attrs_closest_ancestors(attr_parent);
if let Some(first_ancestor) = ink_ancestors.next() {
let parent_ink_scope_valid_ink_macros: Vec<InkMacroKind> = [first_ancestor]
.into_iter()
.chain(ink_ancestors)
.flat_map(|attr| valid_quasi_direct_descendant_ink_macros(*attr.kind(), version))
.collect();
suggestions.retain(|macro_kind| {
!parent_ink_scope_valid_ink_macros.is_empty()
&& parent_ink_scope_valid_ink_macros.contains(macro_kind)
});
}
}
pub fn remove_invalid_ink_macro_suggestions_for_parent_cfg_scope(
suggestions: &mut Vec<InkMacroKind>,
attr_parent: &SyntaxNode,
) {
if suggestions
.iter()
.any(|macro_kind| matches!(macro_kind, InkMacroKind::Test | InkMacroKind::E2ETest))
{
let has_cfg_test_ancestors = attr_parent
.ancestors()
.filter(|ancestor| ancestor != attr_parent)
.any(|node| ink_analyzer_ir::attrs(&node).any(|attr| is_cfg_test_attr(&attr)));
let has_cfg_e2e_test_ancestors = has_cfg_test_ancestors
&& attr_parent
.ancestors()
.filter(|ancestor| ancestor != attr_parent)
.any(|node| ink_analyzer_ir::attrs(&node).any(|attr| is_cfg_e2e_tests_attr(&attr)));
suggestions.retain(|macro_kind| {
!matches!(macro_kind, InkMacroKind::Test | InkMacroKind::E2ETest)
|| (*macro_kind == InkMacroKind::Test && has_cfg_test_ancestors)
|| (*macro_kind == InkMacroKind::E2ETest && has_cfg_e2e_test_ancestors)
});
}
}
pub fn is_cfg_test_attr(attr: &ast::Attr) -> bool {
attr.path()
.is_some_and(|path| path.to_string().trim() == "cfg")
&& attr.token_tree().is_some_and(|token_tree| {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"[(,]\s*test\s*[,)]").unwrap());
RE.is_match(&token_tree.syntax().to_string())
})
}
pub fn is_cfg_e2e_tests_attr(attr: &ast::Attr) -> bool {
is_cfg_test_attr(attr)
&& attr.token_tree().is_some_and(|token_tree| {
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"[(,]\s*feature\s*=\s*"e2e-tests"\s*[,)]"#).unwrap());
RE.is_match(&token_tree.syntax().to_string())
})
}
pub fn ink_arg_insert_text(
arg_kind: InkArgKind,
version: Version,
insert_offset_option: Option<TextSize>,
parent_attr_option: Option<&InkAttribute>,
) -> (String, Option<String>) {
let value_kind = InkArgValueKind::from(arg_kind);
let next_non_trivia_token = || {
parent_attr_option
.map(InkAttribute::syntax)
.zip(insert_offset_option)
.and_then(|(parent_node, insert_offset)| {
parent_node
.token_at_offset(insert_offset)
.right_biased()
.and_then(|token| {
let is_next_non_trivia_token = |subject: &SyntaxToken| {
subject.text_range().start() >= insert_offset
&& !subject.kind().is_trivia()
};
if is_next_non_trivia_token(&token) {
Some(token)
} else {
ink_analyzer_ir::closest_item_which(
&token,
SyntaxToken::next_token,
is_next_non_trivia_token,
is_next_non_trivia_token,
)
}
})
})
};
let insert_equal_token = match value_kind {
InkArgValueKind::None | InkArgValueKind::Arg(..) | InkArgValueKind::Choice(..) => false,
_ => next_non_trivia_token()
.map(|next_token| match next_token.kind() {
SyntaxKind::EQ => false,
_ => true,
})
.unwrap_or(true),
};
let insert_nested_value = !insert_equal_token
&& match value_kind {
InkArgValueKind::Arg(_, false) | InkArgValueKind::Choice(_, _, false) => false,
InkArgValueKind::Arg(_, true) | InkArgValueKind::Choice(_, _, true) => {
next_non_trivia_token()
.map(|next_token| match next_token.kind() {
SyntaxKind::L_PAREN => false,
_ => true,
})
.unwrap_or(true)
}
_ => false,
};
let (text_value, snippet_value) = if insert_equal_token || insert_nested_value {
match arg_kind {
InkArgKind::Abi => (r#""sol""#.to_owned(), r#""${1:sol}""#.to_owned()),
InkArgKind::AdditionalContracts | InkArgKind::KeepAttr | InkArgKind::SignatureTopic => {
(r#""""#.to_owned(), r#""$1""#.to_owned())
}
InkArgKind::Backend => ("node".to_owned(), "${1:node}".to_owned()),
InkArgKind::Derive | InkArgKind::HandleStatus => {
("true".to_owned(), "${1:true}".to_owned())
}
InkArgKind::Env | InkArgKind::Environment => {
let path = parent_attr_option
.and_then(|ink_attr| {
resolution::candidate_adt_by_name_or_external_trait_impl(
"Environment",
&["ink::env", "ink_env"],
ink_attr.syntax(),
None,
)
})
.as_ref()
.and_then(resolution::item_path)
.unwrap_or("ink::env::DefaultEnvironment".to_owned());
let snippet = format!("${{1:{path}}}");
(path, snippet)
}
InkArgKind::Extension => {
let mut unavailable_ids = HashSet::new();
if let Some(ink_attr) = parent_attr_option {
let parent_fn = ink_attr
.syntax()
.parent()
.filter(|parent| ast::Fn::can_cast(parent.kind()));
if let Some(chain_extension) = parent_fn
.as_ref()
.and_then(ink_analyzer_ir::ink_parent::<ChainExtension>)
{
unavailable_ids = chain_extension
.extensions()
.iter()
.filter_map(Extension::id)
.collect();
}
}
let id = suggest_unique_id(None, &unavailable_ids).unwrap_or(1);
(format!("{id}"), format!("${{1:{id}}}"))
}
InkArgKind::Function => {
let mut unavailable_ids = HashSet::new();
if let Some(ink_attr) = parent_attr_option {
let parent_fn = ink_attr
.syntax()
.parent()
.filter(|parent| ast::Fn::can_cast(parent.kind()));
if let Some(chain_extension) = parent_fn
.as_ref()
.and_then(ink_analyzer_ir::ink_parent::<ChainExtension>)
{
unavailable_ids = chain_extension
.functions()
.iter()
.filter_map(Function::id)
.collect();
}
}
let id = suggest_unique_id(None, &unavailable_ids).unwrap_or(1);
(format!("{id}"), format!("${{1:{id}}}"))
}
InkArgKind::Name => (r#""name""#.to_owned(), r#""${1:name}""#.to_owned()),
InkArgKind::Namespace => (
r#""my_namespace""#.to_owned(),
r#""${1:my_namespace}""#.to_owned(),
),
InkArgKind::Sandbox => match version {
Version::Legacy => (String::new(), String::new()),
Version::V5(MinorVersion::Base) => (
"ink_e2e::MinimalSandbox".to_owned(),
"${{1:ink_e2e::MinimalSandbox}}".to_owned(),
),
_ => (
"ink_e2e::DefaultSandbox".to_owned(),
"${{1:ink_e2e::DefaultSandbox}}".to_owned(),
),
},
InkArgKind::Selector => {
let mut unavailable_ids = HashSet::new();
if let Some(ink_attr) = parent_attr_option {
let parent_fn = ink_attr
.syntax()
.parent()
.filter(|parent| ast::Fn::can_cast(parent.kind()));
let parent_trait_def = || {
parent_fn
.as_ref()
.and_then(ink_analyzer_ir::ink_parent::<TraitDefinition>)
};
let parent_contract = || {
parent_fn.as_ref().and_then(|parent_fn| {
ink_analyzer_ir::ink_ancestors::<Contract>(parent_fn).next()
})
};
if let Some(trait_def) = parent_trait_def() {
unavailable_ids = trait_def
.messages()
.iter()
.filter_map(|msg| msg.composed_selector().map(Selector::into_be_u32))
.collect();
} else if let Some(contract) = parent_contract() {
let is_ctor = ink_attr
.args()
.iter()
.any(|arg| *arg.kind() == InkArgKind::Constructor);
unavailable_ids = if is_ctor {
contract
.constructors()
.iter()
.filter_map(|ctor| {
ctor.composed_selector().map(Selector::into_be_u32)
})
.collect()
} else {
contract
.messages()
.iter()
.filter_map(|msg| {
msg.composed_selector().map(Selector::into_be_u32)
})
.collect()
};
}
}
let id = suggest_unique_id(None, &unavailable_ids).unwrap_or(1);
(format!("{id}"), format!("${{1:{id}}}"))
}
InkArgKind::Url => (
r#""ws://127.0.0.1:9000""#.to_owned(),
r#""${1:ws://127.0.0.1:9000}""#.to_owned(),
),
_ => (String::new(), String::new()),
}
} else {
(String::new(), String::new())
};
let eq_sign = if insert_equal_token { " = " } else { "" };
let (l_paren, r_paren) = if insert_nested_value {
("(", ")")
} else {
("", "")
};
let text = format!("{arg_kind}{eq_sign}{l_paren}{text_value}{r_paren}");
let snippet = (insert_equal_token || insert_nested_value)
.then(|| format!("{arg_kind}{eq_sign}{l_paren}{snippet_value}{r_paren}"));
(text, snippet)
}
pub fn ink_attribute_insert_offset(node: &SyntaxNode) -> TextSize {
ink_analyzer_ir::ink_attrs(node)
.last()
.as_ref()
.map(InkAttribute::syntax)
.and_then(SyntaxNode::last_token)
.or_else(|| {
node.children()
.filter_map(ast::Attr::cast)
.last()
.as_ref()
.map(ast::Attr::syntax)
.and_then(SyntaxNode::last_token)
})
.or_else(|| {
node.first_token()
.filter(|first_token| first_token.kind().is_trivia())
})
.and_then(|it| ink_analyzer_ir::closest_non_trivia_token(&it, SyntaxToken::next_token))
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| node.text_range())
.start()
}
pub fn ink_arg_insert_offset_and_affixes(
ink_attr: &InkAttribute,
arg_kind_option: Option<InkArgKind>,
) -> Option<(TextSize, Option<&str>, Option<&str>)> {
let is_primary = arg_kind_option
.as_ref()
.is_some_and(InkArgKind::is_entity_type);
ink_attr.ast().r_brack_token().map(|r_bracket| {
ink_attr
.ast()
.token_tree()
.as_ref()
.map(|token_tree| {
(
if is_primary {
token_tree
.l_paren_token()
.map(|r_paren| r_paren.text_range().end())
.unwrap_or_else(|| token_tree.syntax().text_range().end())
} else {
token_tree
.r_paren_token()
.map(|r_paren| r_paren.text_range().start())
.unwrap_or_else(|| token_tree.syntax().text_range().end())
},
match token_tree.l_paren_token() {
Some(_) => {
if is_primary {
None
} else {
token_tree
.r_paren_token()
.and_then(|r_paren| {
r_paren.prev_token().and_then(|penultimate_token| {
match penultimate_token.kind() {
SyntaxKind::COMMA | SyntaxKind::L_PAREN => None,
_ => Some(", "),
}
})
})
.or_else(|| {
token_tree.syntax().last_token().and_then(|last_token| {
match last_token.kind() {
SyntaxKind::COMMA
| SyntaxKind::L_PAREN
| SyntaxKind::R_PAREN => None,
_ => Some(", "),
}
})
})
}
}
None => Some("("),
},
match token_tree.r_paren_token() {
Some(_) => {
if is_primary {
token_tree
.l_paren_token()
.and_then(|l_paren| {
l_paren.next_token().and_then(|first_token| {
match first_token.kind() {
SyntaxKind::COMMA | SyntaxKind::R_PAREN => None,
_ => Some(", "),
}
})
})
.or_else(|| {
token_tree.syntax().first_token().and_then(|first_token| {
match first_token.kind() {
SyntaxKind::COMMA
| SyntaxKind::L_PAREN
| SyntaxKind::R_PAREN => None,
_ => Some(", "),
}
})
})
} else {
None
}
}
None => Some(")"),
},
)
})
.unwrap_or_else(|| (r_bracket.text_range().start(), Some("("), Some(")")))
})
}
pub fn first_ink_attribute_insert_offset(node: &SyntaxNode) -> TextSize {
ink_analyzer_ir::ink_attrs(node)
.next()
.map(|it| it.syntax().text_range().start())
.unwrap_or_else(|| ink_attribute_insert_offset(node))
}
pub fn first_ink_arg_insert_offset_and_affixes(
ink_attr: &InkAttribute,
) -> Option<(TextSize, Option<&str>, Option<&str>)> {
ink_attr
.args()
.first()
.map(|arg| {
(arg.text_range().start(), None, Some(", "))
})
.or_else(|| {
ink_attr
.ast()
.token_tree()
.as_ref()
.and_then(|token_tree| Some(token_tree).zip(token_tree.l_paren_token()))
.map(|(token_tree, l_paren)| {
(
l_paren.text_range().end(),
None,
match token_tree.r_paren_token() {
Some(_) => None,
None => Some(")"),
},
)
})
})
.or_else(|| {
ink_attr
.ast()
.token_tree()
.as_ref()
.and_then(|token_tree| Some(token_tree).zip(token_tree.r_paren_token()))
.map(|(token_tree, r_paren)| {
(
r_paren.text_range().start(),
match token_tree.l_paren_token() {
Some(_) => None,
None => Some("("),
},
None,
)
})
})
.or_else(|| {
ink_attr.ast().r_brack_token().map(|r_bracket| {
(r_bracket.text_range().start(), Some("("), Some(")"))
})
})
}
pub fn item_indenting(node: &SyntaxNode) -> Option<String> {
node.prev_sibling_or_token().and_then(|prev_elem| {
(prev_elem.kind() == SyntaxKind::WHITESPACE)
.then(|| end_indenting(prev_elem.to_string().as_str()))
})
}
pub fn end_indenting(whitespace: &str) -> String {
whitespace
.chars()
.rev()
.take_while(|char| *char != '\n' && char.is_whitespace())
.collect()
}
pub fn item_children_indenting(node: &SyntaxNode) -> String {
item_indenting(node)
.and_then(|ident| (!ident.is_empty()).then(|| format!("{ident}{ident}")))
.unwrap_or(" ".to_owned())
}
pub fn focused_element<T: InkEntity>(item: &T, range: TextRange) -> Option<SyntaxElement> {
if range.is_empty() {
item.item_at_offset(range.start())
.focused_token()
.cloned()
.map(SyntaxElement::Token)
} else {
item.syntax()
.text_range()
.contains_range(range)
.then(|| {
item.syntax().covering_element(range)
})
}
}
pub fn covering_attribute<T: InkEntity>(item: &T, range: TextRange) -> Option<ast::Attr> {
if range.is_empty() {
item.item_at_offset(range.start()).parent_attr()
} else {
focused_element(item, range).and_then(|covering_element| {
if ast::Attr::can_cast(covering_element.kind()) {
covering_element.into_node().and_then(ast::Attr::cast)
} else {
ink_analyzer_ir::closest_ancestor_ast_type::<SyntaxElement, ast::Attr>(
&covering_element,
)
}
})
}
}
pub fn covering_ink_attribute<T: InkEntity>(item: &T, range: TextRange) -> Option<InkAttribute> {
covering_attribute(item, range).and_then(InkAttribute::cast)
}
pub fn parent_ast_item<T: InkEntity>(item: &T, range: TextRange) -> Option<ast::Item> {
if range.is_empty() {
item.item_at_offset(range.start()).parent_ast_item()
} else {
focused_element(item, range).and_then(|covering_element| {
if ast::Item::can_cast(covering_element.kind()) {
covering_element.into_node().and_then(ast::Item::cast)
} else {
ink_analyzer_ir::parent_ast_item(&covering_element)
}
})
}
}
pub fn ast_item_declaration_range(item: &ast::Item) -> Option<TextRange> {
match item {
ast::Item::Module(module) => module
.item_list()
.map(|it| {
it.l_curly_token()
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| it.syntax().text_range())
})
.or_else(|| {
module
.semicolon_token()
.as_ref()
.map(SyntaxToken::text_range)
}),
ast::Item::Trait(trait_item) => trait_item.assoc_item_list().map(|it| {
it.l_curly_token()
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| it.syntax().text_range())
}),
ast::Item::Impl(impl_item) => impl_item.assoc_item_list().map(|it| {
it.l_curly_token()
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| it.syntax().text_range())
}),
ast::Item::Fn(fn_item) => fn_item
.body()
.map(|it| {
it.stmt_list()
.map(|it| {
it.l_curly_token()
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| it.syntax().text_range())
})
.unwrap_or_else(|| it.syntax().text_range())
})
.or_else(|| {
fn_item
.semicolon_token()
.as_ref()
.map(SyntaxToken::text_range)
}),
ast::Item::Enum(enum_item) => enum_item.variant_list().map(|it| {
it.l_curly_token()
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| it.syntax().text_range())
}),
ast::Item::Struct(struct_item) => struct_item
.field_list()
.map(|it| {
match &it {
ast::FieldList::RecordFieldList(it) => {
it.l_curly_token().as_ref().map(SyntaxToken::text_range)
}
ast::FieldList::TupleFieldList(it) => {
struct_item
.semicolon_token()
.as_ref()
.map(SyntaxToken::text_range)
.or_else(|| it.r_paren_token().as_ref().map(SyntaxToken::text_range))
.or_else(|| Some(it.syntax().text_range()))
}
}
.unwrap_or(it.syntax().text_range())
})
.or_else(|| {
struct_item
.semicolon_token()
.as_ref()
.map(SyntaxToken::text_range)
}),
ast::Item::Union(union_item) => union_item.record_field_list().map(|it| {
it.l_curly_token()
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| it.syntax().text_range())
}),
ast::Item::TypeAlias(type_alias) => type_alias
.semicolon_token()
.as_ref()
.map(SyntaxToken::text_range),
_ => None,
}
.map(TextRange::end)
.map(|end| {
let last_comment = item.doc_comments().last().map(|it| it.syntax().clone());
let last_attr_token = item.attrs().last().and_then(|it| it.syntax().last_token());
let start = last_comment
.as_ref()
.zip(last_attr_token.as_ref())
.map(|(comment, attr)| {
if comment.text_range().end() >= attr.text_range().end() {
comment.clone()
} else {
attr.clone()
}
})
.or(last_comment)
.or(last_attr_token)
.and_then(|it| ink_analyzer_ir::closest_non_trivia_token(&it, SyntaxToken::next_token))
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| item.syntax().text_range())
.start();
TextRange::new(start, end)
})
}
pub fn ast_item_terminal_token(item: &ast::Item) -> Option<SyntaxToken> {
match item {
ast::Item::Module(module) => module
.item_list()
.and_then(|it| it.r_curly_token())
.or_else(|| module.semicolon_token()),
ast::Item::Trait(trait_item) => trait_item
.assoc_item_list()
.and_then(|it| it.r_curly_token()),
ast::Item::Impl(impl_item) => impl_item
.assoc_item_list()
.and_then(|it| it.r_curly_token()),
ast::Item::Fn(fn_item) => fn_item
.body()
.and_then(|it| it.stmt_list().and_then(|it| it.r_curly_token()))
.or_else(|| fn_item.semicolon_token()),
ast::Item::Enum(enum_item) => enum_item.variant_list().and_then(|it| it.r_curly_token()),
ast::Item::Struct(struct_item) => struct_item
.field_list()
.and_then(|it| {
match &it {
ast::FieldList::RecordFieldList(it) => it.r_curly_token(),
ast::FieldList::TupleFieldList(it) => {
struct_item
.semicolon_token()
.or_else(|| it.r_paren_token())
}
}
})
.or_else(|| struct_item.semicolon_token()),
ast::Item::Union(union_item) => union_item
.record_field_list()
.and_then(|it| it.r_curly_token()),
ast::Item::TypeAlias(type_alias) => type_alias.semicolon_token(),
_ => None,
}
}
pub fn token_and_trivia_range(token: &SyntaxToken) -> TextRange {
TextRange::new(
token.text_range().start(),
ink_analyzer_ir::closest_non_trivia_token(token, SyntaxToken::next_token)
.map(|it| it.text_range().start())
.unwrap_or_else(|| token.text_range().end()),
)
}
pub fn node_and_trivia_range(node: &SyntaxNode) -> TextRange {
TextRange::new(
node.text_range().start(),
node.last_token()
.as_ref()
.map(token_and_trivia_range)
.unwrap_or_else(|| node.text_range())
.end(),
)
}
pub fn token_and_delimiter_range(token: &SyntaxToken, delimiter: SyntaxKind) -> TextRange {
let next_delimiter = ink_analyzer_ir::closest_item_which(
token,
SyntaxToken::next_token,
|subject| subject.kind() == delimiter,
|subject| !subject.kind().is_trivia(),
);
TextRange::new(
if next_delimiter.is_none() {
ink_analyzer_ir::closest_item_which(
token,
SyntaxToken::prev_token,
|subject| subject.kind() == delimiter,
|subject| !subject.kind().is_trivia(),
)
} else {
None
}
.as_ref()
.unwrap_or(token)
.text_range()
.start(),
next_delimiter
.as_ref()
.map(SyntaxToken::text_range)
.unwrap_or_else(|| token.text_range())
.end(),
)
}
pub fn node_and_delimiter_range(node: &SyntaxNode, delimiter: SyntaxKind) -> TextRange {
let end = node
.last_token()
.as_ref()
.map(|token| token_and_delimiter_range(token, delimiter))
.unwrap_or_else(|| node.text_range())
.end();
TextRange::new(
if end == node.text_range().end() {
node.first_token()
.map(|token| token_and_delimiter_range(&token, delimiter))
} else {
None
}
.unwrap_or(node.text_range())
.start(),
end,
)
}
pub fn ink_arg_and_delimiter_removal_range(
arg: &InkArg,
parent_attr_option: Option<&InkAttribute>,
) -> TextRange {
let last_token_option = arg.meta().elements().last().and_then(|elem| match elem {
SyntaxElement::Node(node) => node.last_token(),
SyntaxElement::Token(token) => Some(token.clone()),
});
if let Some(attr) = parent_attr_option.cloned().or_else(|| {
last_token_option
.as_ref()
.and_then(|token| {
ink_analyzer_ir::closest_ancestor_ast_type::<SyntaxToken, ast::Attr>(token)
})
.and_then(InkAttribute::cast)
}) {
if attr.args().len() == 1 {
match attr.kind() {
InkAttributeKind::Macro(_) => {
if let Some(token_tree) =
attr.ast().meta().as_ref().and_then(ast::Meta::token_tree)
{
return token_tree.syntax().text_range();
}
}
InkAttributeKind::Arg(_) => {
return attr.syntax().text_range();
}
}
}
}
let end = last_token_option
.as_ref()
.map(|token| token_and_delimiter_range(token, SyntaxKind::COMMA))
.unwrap_or_else(|| arg.text_range())
.end();
TextRange::new(
if end == arg.text_range().end() {
arg.meta()
.name()
.option()
.and_then(|result| match result {
Ok(name) => Some(name.syntax().clone()),
Err(elements) => elements.last().and_then(|elem| match elem {
SyntaxElement::Node(node) => node.first_token(),
SyntaxElement::Token(token) => Some(token.clone()),
}),
})
.or_else(|| arg.meta().eq().map(|eq| eq.syntax().clone()))
.or_else(|| {
arg.meta()
.value()
.option()
.and_then(|result| {
match result {
Ok(value) => value.elements(),
Err(elements) => elements,
}
.first()
})
.and_then(|elem| match elem {
SyntaxElement::Node(node) => node.first_token(),
SyntaxElement::Token(token) => Some(token.clone()),
})
})
} else {
None
}
.as_ref()
.map(|token| token_and_delimiter_range(token, SyntaxKind::COMMA))
.unwrap_or_else(|| arg.text_range())
.start(),
end,
)
}
pub fn item_insert_offset_start(item_list: &ast::ItemList) -> TextSize {
item_list
.items()
.filter(|it| matches!(it, ast::Item::Use(_)))
.last()
.map(|it| it.syntax().text_range().end())
.or_else(|| item_list.l_curly_token().map(|it| it.text_range().end()))
.or_else(|| {
item_list
.items()
.next()
.map(|it| it.syntax().text_range().start())
})
.unwrap_or(item_list.syntax().text_range().start())
}
pub fn item_insert_offset_end(item_list: &ast::ItemList) -> TextSize {
item_list
.items()
.last()
.map(|it| it.syntax().text_range().end())
.unwrap_or_else(|| item_insert_offset_start(item_list))
}
pub fn item_insert_offset_after_last_adt_or_start(item_list: &ast::ItemList) -> TextSize {
item_list
.items()
.filter(|it| {
matches!(
it,
ast::Item::Struct(_) | ast::Item::Enum(_) | ast::Item::Union(_)
)
})
.last()
.map(|it| it.syntax().text_range().end())
.unwrap_or_else(|| item_insert_offset_start(item_list))
}
pub fn item_insert_offset_impl(item_list: &ast::ItemList) -> TextSize {
item_list
.items()
.filter(|it| matches!(it, ast::Item::Impl(_)))
.last()
.or_else(|| {
item_list
.items()
.filter(|it| matches!(it, ast::Item::Struct(_)))
.last()
})
.map(|it| it.syntax().text_range().end())
.or_else(|| {
item_list
.items()
.find(|it| matches!(it, ast::Item::Module(_)))
.map(|it| it.syntax().text_range().start())
})
.unwrap_or(item_insert_offset_end(item_list))
}
pub fn item_insert_offset_by_scope_name(
item_list: &ast::ItemList,
ink_scope_name: &str,
) -> TextSize {
match ink_scope_name {
"storage" => item_insert_offset_start(item_list),
"event" => item_insert_offset_after_last_adt_or_start(item_list),
"impl" => item_insert_offset_impl(item_list),
_ => item_insert_offset_end(item_list),
}
}
pub fn assoc_item_insert_offset_start(assoc_item_list: &ast::AssocItemList) -> TextSize {
assoc_item_list
.l_curly_token()
.map(|it| it.text_range().end())
.or_else(|| {
assoc_item_list
.assoc_items()
.next()
.map(|it| it.syntax().text_range().start())
})
.unwrap_or(assoc_item_list.syntax().text_range().start())
}
pub fn assoc_item_insert_offset_end(assoc_item_list: &ast::AssocItemList) -> TextSize {
assoc_item_list
.assoc_items()
.last()
.map(|it| it.syntax().text_range().end())
.unwrap_or_else(|| assoc_item_insert_offset_start(assoc_item_list))
}
pub fn field_insert_offset_start_and_affixes(
field_list: &ast::FieldList,
) -> (TextSize, Option<String>, Option<String>) {
match field_list {
ast::FieldList::RecordFieldList(record_field_list) => record_field_list
.l_curly_token()
.map(|it| (it.text_range().end(), None, None))
.or_else(|| {
record_field_list.fields().next().map(|it| {
(
it.syntax().text_range().start(),
None,
Some(format!(
"\n{}",
item_children_indenting(field_list.syntax())
)),
)
})
})
.unwrap_or((record_field_list.syntax().text_range().start(), None, None)),
ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list
.l_paren_token()
.map(|it| (it.text_range().end(), None, None))
.or_else(|| {
tuple_field_list.fields().next().map(|it| {
(
it.syntax().text_range().start(),
None,
Some(", ".to_owned()),
)
})
})
.unwrap_or((tuple_field_list.syntax().text_range().start(), None, None)),
}
}
pub fn field_insert_offset_end_and_affixes(
field_list: &ast::FieldList,
) -> (TextSize, Option<String>, Option<String>) {
let insert_after_prefix = |last_field: &SyntaxNode, is_block: bool| {
let has_comma = last_field
.last_token()
.and_then(|token| {
ink_analyzer_ir::closest_item_which(
&token,
SyntaxToken::next_token,
|subject| subject.kind() == SyntaxKind::COMMA,
|subject| !subject.kind().is_trivia(),
)
})
.is_some();
(!has_comma || is_block).then(|| {
format!(
"{}{}",
if has_comma { "" } else { "," },
if is_block { "\n" } else { " " }
)
})
};
match field_list {
ast::FieldList::RecordFieldList(record_field_list) => record_field_list
.fields()
.last()
.map(|it| {
(
node_and_delimiter_range(it.syntax(), SyntaxKind::COMMA).end(),
insert_after_prefix(it.syntax(), true),
None,
)
}),
ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list
.fields()
.last()
.map(|it| {
(
node_and_delimiter_range(it.syntax(), SyntaxKind::COMMA).end(),
insert_after_prefix(it.syntax(), false),
None,
)
}),
}
.unwrap_or(field_insert_offset_start_and_affixes(field_list))
}
pub fn callable_insert_offset_indent_and_affixes(
contract: &Contract,
) -> Option<(TextSize, String, Option<String>, Option<String>)> {
contract
.module()
.and_then(ast::Module::item_list)
.and_then(|it| {
it.items().find_map(|it| match it {
ast::Item::Impl(impl_item) => impl_item.trait_().is_none().then_some(impl_item),
_ => None,
})
})
.as_ref()
.or_else(|| {
contract
.impls()
.iter()
.find(|it| it.trait_type().is_none())
.and_then(InkImpl::impl_item)
})
.and_then(|impl_item| Some(impl_item).zip(impl_item.assoc_item_list()))
.map(|(impl_item, assoc_item_list)| {
(
assoc_item_insert_offset_end(&assoc_item_list),
item_children_indenting(impl_item.syntax()),
None,
None,
)
})
.or_else(|| {
contract
.module()
.and_then(ast::Module::item_list)
.and_then(|item_list| {
callable_impl_indent_and_affixes(contract).map(|(indent, prefix, suffix)| {
(
item_insert_offset_impl(&item_list),
indent,
Some(prefix),
Some(suffix),
)
})
})
})
}
pub fn callable_impl_indent_and_affixes(contract: &Contract) -> Option<(String, String, String)> {
contract.module().and_then(|mod_item| {
resolve_contract_name(contract).map(|name| {
let indent = item_children_indenting(mod_item.syntax());
let prefix = format!("{indent}impl {name} {{\n");
let suffix = format!("\n{indent}}}",);
(format!("{indent} "), prefix, suffix)
})
})
}
pub fn resolve_contract_name(contract: &Contract) -> Option<String> {
contract
.storage()
.and_then(Storage::struct_item)
.and_then(HasName::name)
.as_ref()
.map(ToString::to_string)
.or_else(|| {
contract
.module()
.and_then(HasName::name)
.as_ref()
.map(ToString::to_string)
.as_deref()
.map(utils::pascal_case)
})
}
pub fn apply_indenting(input: &str, indent: &str) -> String {
if indent.is_empty() {
input.to_owned()
} else {
let mut output = String::new();
for (idx, line) in input.lines().enumerate() {
if idx > 0 {
output.push('\n');
}
if !line.is_empty() {
output.push_str(indent);
output.push_str(line);
}
}
output
}
}
pub fn reduce_indenting(input: &str, indent: &str) -> String {
if indent.is_empty() {
input.to_owned()
} else {
let mut output = String::new();
for (idx, line) in input.lines().enumerate() {
if idx > 0 {
output.push('\n');
}
if !line.is_empty() {
output.push_str(line.strip_prefix(indent).unwrap_or(line));
}
}
output
}
}
pub fn starts_with_two_or_more_newlines(text: &str) -> bool {
static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([^\S\n]*\n[^\S\n]*){2,}").unwrap());
RE.is_match(text)
}
pub fn suggest_unique_id_mut<T>(
preferred_id: Option<T>,
unavailable_ids: &mut HashSet<T>,
) -> Option<T>
where
T: IsIntId,
{
let suggested_id = suggest_unique_id(preferred_id, unavailable_ids)?;
unavailable_ids.insert(suggested_id);
Some(suggested_id)
}
pub fn suggest_unique_id<T>(preferred_id: Option<T>, unavailable_ids: &HashSet<T>) -> Option<T>
where
T: IsIntId,
{
let mut suggested_id = preferred_id.unwrap_or(1.into());
while unavailable_ids.contains(&suggested_id) {
if suggested_id == T::MAX {
return None;
}
suggested_id += 1.into();
}
Some(suggested_id)
}
pub fn suggest_unique_name(preferred_name: &str, unavailable_names: &HashSet<String>) -> String {
let mut suggested_name = preferred_name.to_owned();
let mut suffix = 2;
while unavailable_names.contains(&suggested_name) {
if suffix == u8::MAX {
preferred_name.clone_into(&mut suggested_name);
break;
}
suggested_name = format!("{preferred_name}{suffix}");
suffix += 1;
}
suggested_name
}
pub fn contract_declaration_range(contract: &Contract) -> TextRange {
contract
.module()
.and_then(|it| ast_item_declaration_range(&ast::Item::Module(it.clone())))
.unwrap_or(contract.syntax().text_range())
}
pub fn ink_impl_declaration_range(ink_impl: &InkImpl) -> TextRange {
ink_impl
.impl_item()
.and_then(|it| ast_item_declaration_range(&ast::Item::Impl(it.clone())))
.unwrap_or(ink_impl.syntax().text_range())
}
pub fn ink_trait_declaration_range<T>(ink_trait_item: &T) -> TextRange
where
T: IsInkTrait,
{
ink_trait_item
.trait_item()
.and_then(|it| ast_item_declaration_range(&ast::Item::Trait(it.clone())))
.unwrap_or(ink_trait_item.syntax().text_range())
}
pub fn is_trivia_insensitive_eq(a: &SyntaxNode, b: &SyntaxNode) -> bool {
let strip_trivia = |node: &SyntaxNode| {
node.children_with_tokens()
.filter(|node| !node.kind().is_trivia())
.join("")
};
strip_trivia(a) == strip_trivia(b)
}
pub fn is_trait_definition_impl_message(target: &SyntaxNode) -> bool {
Message::can_cast(target)
&& Message::cast(target.clone())
.expect("Should be able to cast to message.")
.parent_impl_item()
.is_some_and(|impl_item| impl_item.trait_().is_some())
}
pub fn token_tree_to_non_delimited_meta_string(token_tree: &ast::TokenTree) -> String {
let r_paren_option = token_tree.r_paren_token();
token_tree
.syntax()
.children_with_tokens()
.skip(usize::from(token_tree.l_paren_token().is_some()))
.take_while(|it| r_paren_option.is_none() || it.as_token() != r_paren_option.as_ref())
.join("")
}