Skip to main content

smelt_stdlib/
rules.rs

1//! Rule identities and source-shape metadata for standard-library mappings.
2
3use crate::BackendDependency;
4
5/// Source language that produced a standard-library call shape.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7#[non_exhaustive]
8pub enum SourceLanguage {
9    /// TypeScript or JavaScript input.
10    TypeScript,
11    /// Python input.
12    Python,
13}
14
15/// Broad API namespace for a standard-library rule.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17#[non_exhaustive]
18pub enum ApiNamespace {
19    /// JSON parse/stringify APIs.
20    Json,
21    /// Regular-expression APIs.
22    Regex,
23    /// Random-number APIs.
24    Random,
25    /// HTTP client APIs.
26    Http,
27    /// Date and datetime APIs.
28    DateTime,
29    /// URL parsing and field APIs.
30    Url,
31}
32
33/// Receiver shape for a source API.
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35#[non_exhaustive]
36pub enum ReceiverKind {
37    /// No receiver, such as `fetch(url)`.
38    FreeFunction,
39    /// Static namespace receiver, such as `JSON.parse`.
40    Namespace,
41    /// Instance receiver, such as `new RegExp(pattern).test(text)`.
42    Instance,
43}
44
45/// Source API call shape.
46#[derive(Clone, Copy, Debug, Eq, PartialEq)]
47#[non_exhaustive]
48pub enum ApiShape {
49    /// A function or method call.
50    Call,
51    /// A constructor call.
52    Constructor,
53    /// A property access.
54    Property,
55}
56
57/// Argument shape required by a supported rule.
58#[derive(Clone, Copy, Debug, Eq, PartialEq)]
59#[non_exhaustive]
60pub enum ArgShape {
61    /// Exactly one value argument.
62    OneValue,
63    /// Exactly one string argument.
64    OneString,
65    /// Exactly two string arguments.
66    TwoStrings,
67    /// Exactly two integer arguments.
68    TwoInts,
69    /// Exactly one numeric argument.
70    OneNumber,
71    /// No arguments.
72    None,
73}
74
75/// Return shape produced by a standard-library rule.
76#[derive(Clone, Copy, Debug, Eq, PartialEq)]
77#[non_exhaustive]
78pub enum ReturnShape {
79    /// Boolean result.
80    Bool,
81    /// String result.
82    String,
83    /// Floating-point result.
84    Float,
85    /// Integer result.
86    Int,
87    /// Type is supplied by call-site context.
88    Contextual,
89}
90
91/// Side-effect profile of a standard-library rule.
92#[derive(Clone, Copy, Debug, Eq, PartialEq)]
93#[non_exhaustive]
94pub enum EffectKind {
95    /// Pure deterministic mapping.
96    Pure,
97    /// Random-value generation.
98    Random,
99    /// HTTP or external IO.
100    Io,
101}
102
103/// Stable identity for a recognized standard-library lowering rule.
104#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
105#[non_exhaustive]
106pub enum RuleId {
107    /// TypeScript `JSON.stringify(value)`.
108    TsJsonStringify,
109    /// TypeScript `JSON.parse<T>(text)`.
110    TsJsonParse,
111    /// TypeScript `new RegExp(pattern).test(text)`.
112    TsRegExpTest,
113    /// TypeScript `Math.random()`.
114    TsMathRandom,
115    /// TypeScript `fetch(url)`.
116    TsFetch,
117    /// TypeScript `Date.now()`.
118    TsDateNow,
119    /// TypeScript `new Date(timestamp).toISOString()`.
120    TsDateToIsoString,
121    /// TypeScript `new URL(text).field`.
122    TsUrlField,
123    /// TypeScript `structuredClone(value)`.
124    TsStructuredClone,
125    /// TypeScript `Promise.resolve`, `Promise.all`, `Promise.race`, or `Promise.allSettled`.
126    TsPromiseStatic,
127    /// TypeScript global primitive conversion or numeric parse call.
128    TsPrimitiveCast,
129    /// TypeScript `Symbol(...)` or `Symbol.for(...)`.
130    TsSymbol,
131    /// TypeScript deterministic numeric `Math.*` call.
132    TsMathNumeric,
133    /// TypeScript numeric `Number.*` predicate.
134    TsNumberPredicate,
135    /// TypeScript `Number.parseFloat(...)`.
136    TsNumberParseFloat,
137    /// TypeScript `Number.parseInt(...)`.
138    TsNumberParseInt,
139    /// TypeScript supported static `Object.*` call.
140    TsObjectStatic,
141    /// TypeScript supported static `Array.*` call.
142    TsArrayStatic,
143    /// TypeScript supported static `Buffer.*` call
144    /// (`Buffer.from`/`Buffer.alloc`/`Buffer.concat`/`Buffer.isBuffer`).
145    TsBufferStatic,
146    /// TypeScript `Map.prototype.has`.
147    TsMapHas,
148    /// TypeScript `Map.prototype.get`.
149    TsMapGet,
150    /// TypeScript `Map` mutating method.
151    TsMapMutation,
152    /// TypeScript `Map` projection method.
153    TsMapProjection,
154    /// TypeScript `Set.prototype.has`.
155    TsSetHas,
156    /// TypeScript `Set` mutating method.
157    TsSetMutation,
158    /// TypeScript `Set` projection method.
159    TsSetProjection,
160    /// Python `json.dumps(value)`.
161    PyJsonDumps,
162    /// Python `json.loads(text)`.
163    PyJsonLoads,
164    /// Python `re.search(pattern, text)`.
165    PyReSearch,
166    /// Python `re.match(pattern, text)`.
167    PyReMatch,
168    /// Python `re.fullmatch(pattern, text)`.
169    PyReFullMatch,
170    /// Python `random.random()`.
171    PyRandomRandom,
172    /// Python `random.randint(start, end)`.
173    PyRandomRandInt,
174    /// Python `random.choice(values)`.
175    PyRandomChoice,
176    /// Python `requests.get(url)`.
177    PyRequestsGet,
178    /// Python `datetime.datetime.now()` or `utcnow()`.
179    PyDateTimeNow,
180    /// Python `datetime.datetime.fromtimestamp(seconds)`.
181    PyDateTimeFromTimestamp,
182    /// Python `urllib.parse.urlparse(text).field`.
183    PyUrlparseField,
184}
185
186impl RuleId {
187    /// Return the backend dependency required by this rule, when any.
188    #[must_use]
189    pub const fn backend_dependency(self) -> Option<BackendDependency> {
190        match self {
191            Self::TsJsonStringify | Self::TsJsonParse | Self::PyJsonDumps | Self::PyJsonLoads => {
192                Some(BackendDependency::SerdeJson)
193            }
194            Self::TsRegExpTest | Self::PyReSearch | Self::PyReMatch | Self::PyReFullMatch => {
195                Some(BackendDependency::Regex)
196            }
197            Self::TsMathRandom
198            | Self::PyRandomRandom
199            | Self::PyRandomRandInt
200            | Self::PyRandomChoice => Some(BackendDependency::Rand),
201            Self::TsFetch | Self::PyRequestsGet => Some(BackendDependency::Reqwest),
202            Self::TsDateNow
203            | Self::TsDateToIsoString
204            | Self::PyDateTimeNow
205            | Self::PyDateTimeFromTimestamp => Some(BackendDependency::Chrono),
206            Self::TsUrlField | Self::PyUrlparseField => Some(BackendDependency::Url),
207            Self::TsStructuredClone
208            | Self::TsPromiseStatic
209            | Self::TsPrimitiveCast
210            | Self::TsSymbol
211            | Self::TsMathNumeric
212            | Self::TsNumberPredicate
213            | Self::TsNumberParseFloat
214            | Self::TsNumberParseInt
215            | Self::TsObjectStatic
216            | Self::TsArrayStatic
217            | Self::TsBufferStatic
218            | Self::TsMapHas
219            | Self::TsMapGet
220            | Self::TsMapMutation
221            | Self::TsMapProjection
222            | Self::TsSetHas
223            | Self::TsSetMutation
224            | Self::TsSetProjection => None,
225        }
226    }
227
228    /// Return a concise source API name for diagnostics.
229    #[must_use]
230    pub const fn source_api(self) -> &'static str {
231        match self {
232            Self::TsJsonStringify => "JSON.stringify",
233            Self::TsJsonParse => "JSON.parse",
234            Self::TsRegExpTest => "RegExp.test",
235            Self::TsMathRandom => "Math.random",
236            Self::TsFetch => "fetch",
237            Self::TsDateNow => "Date.now",
238            Self::TsDateToIsoString => "Date.toISOString",
239            Self::TsUrlField => "URL field access",
240            Self::TsStructuredClone => "structuredClone",
241            Self::TsPromiseStatic => "Promise static method",
242            Self::TsPrimitiveCast => "primitive conversion",
243            Self::TsSymbol => "Symbol",
244            Self::TsMathNumeric => "Math numeric method",
245            Self::TsNumberPredicate => "Number predicate",
246            Self::TsNumberParseFloat => "Number.parseFloat",
247            Self::TsNumberParseInt => "Number.parseInt",
248            Self::TsObjectStatic => "Object static method",
249            Self::TsArrayStatic => "Array static method",
250            Self::TsBufferStatic => "Buffer static method",
251            Self::TsMapHas => "Map.has",
252            Self::TsMapGet => "Map.get",
253            Self::TsMapMutation => "Map mutation method",
254            Self::TsMapProjection => "Map projection method",
255            Self::TsSetHas => "Set.has",
256            Self::TsSetMutation => "Set mutation method",
257            Self::TsSetProjection => "Set projection method",
258            Self::PyJsonDumps => "json.dumps",
259            Self::PyJsonLoads => "json.loads",
260            Self::PyReSearch => "re.search",
261            Self::PyReMatch => "re.match",
262            Self::PyReFullMatch => "re.fullmatch",
263            Self::PyRandomRandom => "random.random",
264            Self::PyRandomRandInt => "random.randint",
265            Self::PyRandomChoice => "random.choice",
266            Self::PyRequestsGet => "requests.get",
267            Self::PyDateTimeNow => "datetime.datetime.now",
268            Self::PyDateTimeFromTimestamp => "datetime.datetime.fromtimestamp",
269            Self::PyUrlparseField => "urllib.parse.urlparse field access",
270        }
271    }
272}
273
274#[cfg(test)]
275mod tests {
276    use super::*;
277
278    /// Assert that newly registry-backed Chrono and URL rules expose stable
279    /// dependency and source API metadata for frontend diagnostics/codegen.
280    #[test]
281    fn dependency_backed_date_and_url_rules_report_metadata() {
282        let cases = [
283            (RuleId::TsDateNow, BackendDependency::Chrono, "Date.now"),
284            (
285                RuleId::TsDateToIsoString,
286                BackendDependency::Chrono,
287                "Date.toISOString",
288            ),
289            (
290                RuleId::PyDateTimeNow,
291                BackendDependency::Chrono,
292                "datetime.datetime.now",
293            ),
294            (
295                RuleId::PyDateTimeFromTimestamp,
296                BackendDependency::Chrono,
297                "datetime.datetime.fromtimestamp",
298            ),
299            (
300                RuleId::TsUrlField,
301                BackendDependency::Url,
302                "URL field access",
303            ),
304            (
305                RuleId::PyUrlparseField,
306                BackendDependency::Url,
307                "urllib.parse.urlparse field access",
308            ),
309        ];
310
311        for (rule, dependency, source_api) in cases {
312            assert_eq!(rule.backend_dependency(), Some(dependency));
313            assert_eq!(rule.source_api(), source_api);
314        }
315    }
316}