Skip to main content

zerodds_idl_java/
keywords.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2026 ZeroDDS Contributors
3//! Java-17 keywords (incl. restricted identifiers like `record`, `sealed`,
4//! `var`, `yield`).
5//!
6//! Source: JLS 17 §3.9 + §3.8 (restricted identifiers).
7//!
8//! When an IDL identifier hits a Java keyword, the generator cannot
9//! avoid it via `@`-escape (Java has no such syntax).
10//! Instead the name is given an underscore suffix
11//! (`class` → `class_`).
12
13use crate::error::JavaGenError;
14
15/// Reserved Java 17 keywords (incl. restricted identifiers).
16///
17/// Source: JLS 17 §3.9 ("Keywords"), §3.8 ("Identifiers"; restricted)
18/// and §3.10.3 (Boolean / null literals).
19pub(crate) const JAVA_RESERVED: &[&str] = &[
20    // 50 echte Keywords (JLS §3.9)
21    "abstract",
22    "assert",
23    "boolean",
24    "break",
25    "byte",
26    "case",
27    "catch",
28    "char",
29    "class",
30    "const",
31    "continue",
32    "default",
33    "do",
34    "double",
35    "else",
36    "enum",
37    "extends",
38    "final",
39    "finally",
40    "float",
41    "for",
42    "goto",
43    "if",
44    "implements",
45    "import",
46    "instanceof",
47    "int",
48    "interface",
49    "long",
50    "native",
51    "new",
52    "package",
53    "private",
54    "protected",
55    "public",
56    "return",
57    "short",
58    "static",
59    "strictfp",
60    "super",
61    "switch",
62    "synchronized",
63    "this",
64    "throw",
65    "throws",
66    "transient",
67    "try",
68    "void",
69    "volatile",
70    "while",
71    // Boolean / null literals (§3.10.3) — cannot be used as
72    // identifiers.
73    "true",
74    "false",
75    "null",
76    // Restricted identifiers (§3.8) — reserved within certain
77    // contexts; we conservatively treat them as reserved.
78    "record",
79    "sealed",
80    "var",
81    "yield",
82    "non-sealed",
83    "permits",
84    "exports",
85    "module",
86    "open",
87    "opens",
88    "provides",
89    "requires",
90    "to",
91    "transitive",
92    "uses",
93    "with",
94];
95
96/// Checks whether an identifier is a Java keyword.
97#[must_use]
98pub fn is_reserved(name: &str) -> bool {
99    JAVA_RESERVED.contains(&name)
100}
101
102/// Sanitizes an IDL identifier for Java.
103///
104/// If the name is reserved, a `_` suffix is appended
105/// (`class` → `class_`). Otherwise it stays unchanged.
106///
107/// # Errors
108/// Returns [`JavaGenError::InvalidName`] if the name is empty.
109pub fn sanitize_identifier(name: &str) -> Result<String, JavaGenError> {
110    if name.is_empty() {
111        return Err(JavaGenError::InvalidName {
112            name: name.to_string(),
113            reason: "empty identifier".to_string(),
114        });
115    }
116    if is_reserved(name) {
117        Ok(format!("{name}_"))
118    } else {
119        Ok(name.to_string())
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    #![allow(clippy::expect_used, clippy::panic)]
126    use super::*;
127
128    #[test]
129    fn class_is_reserved() {
130        assert!(is_reserved("class"));
131    }
132
133    #[test]
134    fn record_is_reserved() {
135        assert!(is_reserved("record"));
136    }
137
138    #[test]
139    fn sealed_is_reserved() {
140        assert!(is_reserved("sealed"));
141    }
142
143    #[test]
144    fn var_yield_reserved() {
145        assert!(is_reserved("var"));
146        assert!(is_reserved("yield"));
147    }
148
149    #[test]
150    fn foo_is_not_reserved() {
151        assert!(!is_reserved("Foo"));
152    }
153
154    #[test]
155    fn sanitize_keyword_appends_underscore() {
156        assert_eq!(sanitize_identifier("class").expect("ok"), "class_");
157        assert_eq!(sanitize_identifier("int").expect("ok"), "int_");
158    }
159
160    #[test]
161    fn sanitize_normal_passthrough() {
162        assert_eq!(sanitize_identifier("Foo").expect("ok"), "Foo");
163    }
164
165    #[test]
166    fn sanitize_empty_errors() {
167        assert!(matches!(
168            sanitize_identifier(""),
169            Err(JavaGenError::InvalidName { .. })
170        ));
171    }
172
173    #[test]
174    fn list_contains_at_least_50_keywords() {
175        // JLS §3.9 has 50 real keywords; in total with restricted IDs
176        // we expect > 60.
177        assert!(JAVA_RESERVED.len() >= 50);
178    }
179}