Skip to main content

xidl_parser/lint/
registry.rs

1/// Declaration category used by annotation completion and placement checks.
2#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum Site {
4    /// An IDL module.
5    Module,
6    /// A struct declaration.
7    Struct,
8    /// A union declaration.
9    Union,
10    /// An enum declaration.
11    Enum,
12    /// A bitset declaration.
13    Bitset,
14    /// A bitmask declaration.
15    Bitmask,
16    /// A typedef declaration.
17    Typedef,
18    /// A native declaration.
19    Native,
20    /// A constant declaration.
21    Constant,
22    /// An exception declaration.
23    Exception,
24    /// A struct or exception member.
25    Member,
26    /// A union branch member.
27    UnionMember,
28    /// An enum value.
29    Enumerator,
30    /// A bitmask value.
31    BitmaskValue,
32    /// An interface declaration.
33    Interface,
34    /// An operation declaration.
35    Operation,
36    /// An operation parameter.
37    Parameter,
38    /// An interface attribute.
39    Attribute,
40}
41
42/// Syntactic argument contract, independent of generation and application policy.
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum ArgumentShape {
45    /// No arguments.
46    Marker,
47    /// A value, optionally named `value`.
48    Value,
49    /// An optional value, optionally named `value`.
50    OptionalValue,
51    /// One string, optionally under one of these names.
52    String(&'static [&'static str]),
53    /// An optional string, optionally under one of these names.
54    OptionalString(&'static [&'static str]),
55    /// A single scoped version member reference.
56    Lifecycle,
57    /// A version marker, or `in` and `name` together.
58    Version,
59    /// Required `in` and `name` arguments.
60    ApiKey,
61    /// HTTP status on exceptions, or a marker on representation unions.
62    Http,
63    /// Named arguments with the specified required and optional keys.
64    Named {
65        /// Keys required by this annotation.
66        required: &'static [&'static str],
67        /// Additional accepted keys.
68        optional: &'static [&'static str],
69    },
70    /// One or more positional values, as accepted by target-specific derives.
71    Values,
72    /// Origins accepted by the HTTP CORS contract.
73    Cors,
74    /// A deprecation marker with optional since and after metadata.
75    Deprecated,
76    /// Arguments already validated by the concrete IDL grammar.
77    Grammar,
78}
79
80/// A built-in annotation's public completion and validation metadata.
81#[derive(Debug, Clone, Copy)]
82pub struct AnnotationDefinition {
83    /// Canonical ASCII name; matching ignores ASCII case.
84    pub name: &'static str,
85    /// Typed identity for semantic consumers.
86    pub kind: AnnotationKind,
87    /// Allowed declaration sites; empty means every annotation-bearing site.
88    pub sites: &'static [Site],
89    /// Accepted argument shape.
90    pub arguments: ArgumentShape,
91}
92
93impl AnnotationDefinition {
94    /// Looks up a built-in name using the shared ASCII case rule.
95    pub fn lookup(name: &str) -> Option<&'static Self> {
96        BUILTIN_ANNOTATIONS
97            .iter()
98            .find(|entry| entry.name.eq_ignore_ascii_case(name))
99    }
100
101    /// Whether this definition can annotate the given declaration.
102    pub fn allows(&self, site: Site) -> bool {
103        self.sites.is_empty() || self.sites.contains(&site)
104    }
105
106    /// Whether the name is a supported target-specific pass-through attribute.
107    pub fn is_passthrough(name: &str) -> bool {
108        let name = name.to_ascii_lowercase();
109        ["rust-", "rust_", "ts-", "ts_"]
110            .iter()
111            .any(|prefix| name.starts_with(prefix))
112    }
113}
114
115macro_rules! annotations {
116    ($($kind:ident, $name:literal, $sites:expr, $args:expr;)+) => {
117        /// Typed identity of an annotation from the shared built-in table.
118        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
119        pub enum AnnotationKind {
120            $(#[doc = concat!("The `@", $name, "` annotation.")] $kind,)+
121        }
122        /// Built-in annotations shared by lint, semantic consumers and editors.
123        pub const BUILTIN_ANNOTATIONS: &[AnnotationDefinition] = &[
124            $(AnnotationDefinition { name: $name, kind: AnnotationKind::$kind,
125                sites: $sites, arguments: $args },)+
126        ];
127    };
128}
129
130use ArgumentShape::*;
131use Site::*;
132
133const TYPES: &[Site] = &[
134    Struct, Union, Enum, Bitset, Bitmask, Typedef, Native, Exception,
135];
136const FIELDS: &[Site] = &[Member, UnionMember, Parameter];
137const SECURITY: &[Site] = &[Interface, Operation, Attribute];
138const LIFECYCLE: &[Site] = &[
139    Module,
140    Struct,
141    Union,
142    Enum,
143    Bitset,
144    Bitmask,
145    Typedef,
146    Native,
147    Exception,
148    Member,
149    UnionMember,
150    Enumerator,
151    BitmaskValue,
152    Interface,
153    Operation,
154    Parameter,
155    Attribute,
156];
157
158annotations! {
159    Id, "id", &[Member, UnionMember], Grammar;
160    AutoId, "autoid", &[Module, Struct, Union, Exception], Grammar;
161    Optional, "optional", FIELDS, OptionalValue;
162    Position, "position", &[BitmaskValue], Grammar;
163    Value, "value", &[Enumerator], Grammar;
164    Extensibility, "extensibility", TYPES, Grammar;
165    Final, "final", TYPES, Marker;
166    Appendable, "appendable", TYPES, Marker;
167    Mutable, "mutable", TYPES, Marker;
168    Key, "key", &[Member, UnionMember], OptionalValue;
169    MustUnderstand, "must_understand", &[Member, UnionMember], OptionalValue;
170    DefaultLiteral, "default_literal", &[Enumerator], Marker;
171    Default, "default", FIELDS, Value;
172    Range, "range", FIELDS, Named { required: &["min", "max"], optional: &[] };
173    Min, "min", FIELDS, Value;
174    Max, "max", FIELDS, Value;
175    Unit, "unit", FIELDS, Value;
176    BitBound, "bit_bound", &[Enum, Bitmask], Value;
177    External, "external", FIELDS, OptionalValue;
178    Nested, "nested", TYPES, OptionalValue;
179    Verbatim, "verbatim", &[], Grammar;
180    Service, "service", &[Interface], Grammar;
181    Oneway, "oneway", &[Operation], OptionalValue;
182    Ami, "ami", &[Interface, Operation], OptionalValue;
183    HashId, "hashid", &[Member, UnionMember], OptionalValue;
184    DefaultNested, "default_nested", &[Module], OptionalValue;
185    IgnoreLiteralNames, "ignore_literal_names", &[Enum], OptionalValue;
186    TryConstruct, "try_construct", FIELDS, Grammar;
187    NonSerialized, "non_serialized", FIELDS, OptionalValue;
188    DataRepresentation, "data_representation", TYPES, Grammar;
189    Topic, "topic", &[Struct, Union], Grammar;
190    Choice, "choice", &[Union], Marker;
191    Empty, "empty", &[Struct], Marker;
192    DdsService, "dds_service", &[Interface], Marker;
193    DdsRequestTopic, "dds_request_topic", &[Operation], Grammar;
194    DdsReplyTopic, "dds_reply_topic", &[Operation], Grammar;
195    Rename, "rename", &[], String(&["name"]);
196    Name, "name", &[], String(&["name"]);
197    RenameAll, "rename_all", &[Struct, Union, Enum, Bitmask, Exception, Interface], String(&["rule"]);
198    Skip, "skip", &[Member, UnionMember, Enumerator, BitmaskValue, Parameter], Marker;
199    OmitEmpty, "omitempty", &[Member], Marker;
200    Doc, "doc", &[], String(&["text", "doc"]);
201    Derive, "derive", TYPES, Values;
202    Rust, "rust", &[], Value;
203    Version, "version", &[Enum], Version;
204    Added, "added", LIFECYCLE, Lifecycle;
205    Removed, "removed", LIFECYCLE, Lifecycle;
206    Http, "http", &[Exception, Union], Http;
207    Get, "get", &[Operation], OptionalString(&["path"]);
208    Post, "post", &[Operation], OptionalString(&["path"]);
209    Put, "put", &[Operation], OptionalString(&["path"]);
210    Patch, "patch", &[Operation], OptionalString(&["path"]);
211    Delete, "delete", &[Operation], OptionalString(&["path"]);
212    Head, "head", &[Operation], OptionalString(&["path"]);
213    Options, "options", &[Operation], OptionalString(&["path"]);
214    Path, "path", &[Interface, Operation, Parameter], OptionalString(&["name", "path"]);
215    Query, "query", &[Parameter], OptionalString(&["name"]);
216    Header, "header", &[Parameter, Member], OptionalString(&["name"]);
217    Cookie, "cookie", &[Parameter, Member], OptionalString(&["name"]);
218    Body, "body", &[Parameter, Member], OptionalString(&[]);
219    Flatten, "flatten", &[Member, Parameter], Marker;
220    HttpBasic, "http_basic", SECURITY, OptionalString(&["realm"]);
221    HttpBearer, "http_bearer", SECURITY, Marker;
222    ApiKey, "api_key", SECURITY, ApiKey;
223    NoSecurity, "no_security", SECURITY, Marker;
224    Request, "request", &[Interface, Operation], String(&["format"]);
225    Response, "response", &[Interface, Operation], String(&["format"]);
226    ServerStream, "server_stream", &[Operation, Attribute], Marker;
227    ClientStream, "client_stream", &[Operation], Marker;
228    BidiStream, "bidi_stream", &[Operation], Marker;
229    StreamCodec, "stream_codec", &[Operation], OptionalString(&[]);
230    Upgrade, "upgrade", &[Operation], Named { required: &["protocol"], optional: &["codec", "subprotocol", "heartbeat", "max_message"] };
231    Deprecated, "deprecated", SECURITY, Deprecated;
232    Cors, "cors", &[Interface, Operation], Cors;
233}
234
235impl AnnotationKind {
236    /// Recognizes a name without retaining a second spelling table.
237    pub fn from_name(name: &str) -> Option<Self> {
238        AnnotationDefinition::lookup(name).map(|entry| entry.kind)
239    }
240
241    /// Whether projection removes this annotation after evaluating the timeline.
242    pub fn is_lifecycle(self) -> bool {
243        matches!(self, Self::Version | Self::Added | Self::Removed)
244    }
245}