1mod ast;
16
17pub use ast::*;
18
19use quick_js::Context;
20
21const FLOW_PARSER_JS: &str = include_str!(concat!(env!("OUT_DIR"), "/flow_parser.js"));
22
23#[derive(Debug)]
25pub enum Error {
26 Runtime(String),
28 Parse(Vec<ParseError>),
30 Deserialize(String),
32}
33
34impl std::fmt::Display for Error {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 match self {
37 Self::Runtime(msg) => write!(f, "runtime error: {msg}"),
38 Self::Parse(errors) => {
39 let msgs: Vec<&str> = errors.iter().map(|e| e.message.as_str()).collect();
40 write!(f, "parse errors: {}", msgs.join("; "))
41 }
42 Self::Deserialize(msg) => write!(f, "AST deserialization error: {msg}"),
43 }
44 }
45}
46
47impl std::error::Error for Error {}
48
49pub struct FlowParser {
53 ctx: Context,
54}
55
56impl FlowParser {
57 pub fn new() -> Result<Self, Error> {
61 let ctx = Context::new().map_err(|e| Error::Runtime(e.to_string()))?;
62
63 ctx.eval(
64 "var exports = {}; \
65 var console = { log: function(){}, error: function(){}, warn: function(){} };",
66 )
67 .map_err(|e| Error::Runtime(e.to_string()))?;
68
69 ctx.eval(FLOW_PARSER_JS)
70 .map_err(|e| Error::Runtime(e.to_string()))?;
71
72 Ok(Self { ctx })
73 }
74
75 pub fn parse_json(&self, source: &str) -> Result<String, Error> {
77 let source_json =
78 serde_json::to_string(source).map_err(|e| Error::Runtime(e.to_string()))?;
79
80 let script = format!(
81 "(function() {{\
82 var ast = exports.parse({source_json}, {{ types: true, enums: true }});\
83 return JSON.stringify(ast);\
84 }})()"
85 );
86
87 self.ctx
88 .eval_as(&script)
89 .map_err(|e| Error::Runtime(e.to_string()))
90 }
91
92 pub fn parse(&self, source: &str) -> Result<Program, Error> {
94 let json = self.parse_json(source)?;
95
96 let program: Program =
97 serde_json::from_str(&json).map_err(|e| Error::Deserialize(e.to_string()))?;
98
99 if !program.errors.is_empty() {
100 return Err(Error::Parse(program.errors));
101 }
102
103 Ok(program)
104 }
105
106 pub fn validate(&self, source: &str) -> Result<(), Error> {
110 self.parse(source).map(|_| ())
111 }
112
113 pub fn diagnostics(&self, source: &str) -> Result<Vec<ParseError>, Error> {
119 let json = self.parse_json(source)?;
120 let program: Program =
121 serde_json::from_str(&json).map_err(|e| Error::Deserialize(e.to_string()))?;
122 Ok(program.errors)
123 }
124
125 pub fn validate_declaration(&self, decl: &str) -> Result<Program, Error> {
136 let source = if decl.starts_with("declare export") {
137 format!("// @flow\n{decl}\n")
138 } else {
139 format!("// @flow\nexport {decl}\n")
140 };
141 self.parse(&source)
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use super::*;
148
149 #[test]
150 fn parse_type_alias() {
151 let parser = FlowParser::new().expect("failed to create parser");
153
154 let program = parser
156 .parse("export type Foo = string;")
157 .expect("failed to parse");
158
159 assert_eq!(program.body.len(), 1, "should have one statement");
161 match &program.body[0] {
162 Statement::ExportNamedDeclaration {
163 declaration: Some(Declaration::TypeAlias { id, right }),
164 } => {
165 assert_eq!(id.name, "Foo", "type name");
166 assert_eq!(right.type_name(), "StringTypeAnnotation", "right-hand side");
167 }
168 other => panic!("unexpected statement: {other:?}"),
169 }
170 }
171
172 #[test]
173 fn parse_exact_object() {
174 let parser = FlowParser::new().expect("failed to create parser");
176
177 let program = parser
179 .parse("export type T = {| +name: string, +age: number |};")
180 .expect("failed to parse");
181
182 match &program.body[0] {
184 Statement::ExportNamedDeclaration {
185 declaration: Some(Declaration::TypeAlias { right, .. }),
186 } => match right {
187 TypeAnnotation::ObjectTypeAnnotation {
188 properties, exact, ..
189 } => {
190 assert!(*exact, "should be exact object");
191 assert_eq!(properties.len(), 2, "should have 2 properties");
192 }
193 other => panic!("expected ObjectTypeAnnotation, got: {}", other.type_name()),
194 },
195 other => panic!("unexpected statement: {other:?}"),
196 }
197 }
198
199 #[test]
200 fn parse_error_reported() {
201 let parser = FlowParser::new().expect("failed to create parser");
203
204 let result = parser.parse("type = ;");
206
207 assert!(result.is_err(), "should report parse error");
209 }
210
211 #[test]
212 fn parser_reusable() {
213 let parser = FlowParser::new().expect("failed to create parser");
215
216 assert!(parser.parse("type A = string;").is_ok(), "first parse");
218 assert!(parser.parse("type B = number;").is_ok(), "second parse");
219 }
220
221 #[test]
222 fn validate_valid_source() {
223 let parser = FlowParser::new().expect("failed to create parser");
225
226 assert!(
228 parser.validate("type T = {| +x: number |}").is_ok(),
229 "valid Flow should pass"
230 );
231 }
232
233 #[test]
234 fn validate_invalid_source() {
235 let parser = FlowParser::new().expect("failed to create parser");
237
238 assert!(
240 parser.validate("type = ;").is_err(),
241 "invalid Flow should fail"
242 );
243 }
244
245 #[test]
246 fn validate_declaration_type_alias() {
247 let parser = FlowParser::new().expect("failed to create parser");
249
250 let program = parser
252 .validate_declaration("type Foo = string;")
253 .expect("should parse");
254
255 match &program.body[0] {
257 Statement::ExportNamedDeclaration {
258 declaration: Some(Declaration::TypeAlias { id, .. }),
259 } => {
260 assert_eq!(id.name, "Foo", "type name preserved");
261 }
262 other => panic!("unexpected statement: {other:?}"),
263 }
264 }
265
266 #[test]
267 fn validate_declaration_opaque() {
268 let parser = FlowParser::new().expect("failed to create parser");
270
271 let program = parser
273 .validate_declaration("declare export opaque type Token;")
274 .expect("should parse");
275
276 match &program.body[0] {
278 Statement::DeclareExportDeclaration {
279 declaration: Some(decl),
280 } => match decl {
281 Declaration::OpaqueType { id, .. } | Declaration::DeclareOpaqueType { id, .. } => {
282 assert_eq!(id.name, "Token", "opaque type name");
283 }
284 other => panic!("expected OpaqueType, got: {other:?}"),
285 },
286 other => panic!("unexpected statement: {other:?}"),
287 }
288 }
289
290 #[test]
291 fn diagnostics_valid_source_returns_empty() {
292 let parser = FlowParser::new().expect("failed to create parser");
294
295 let diags = parser
297 .diagnostics("// @flow\nexport type Foo = string;")
298 .expect("runtime error");
299
300 assert!(
302 diags.is_empty(),
303 "valid source should produce no diagnostics"
304 );
305 }
306
307 #[test]
308 fn diagnostics_error_without_loc_deserializes() {
309 let payload = r#"{"type":"Program","body":[],"errors":[{"message":"synthetic error"}]}"#;
311
312 let program: Program = serde_json::from_str(payload).expect("should deserialize");
314
315 assert_eq!(program.errors.len(), 1, "should have one error");
317 assert_eq!(
318 program.errors[0].message, "synthetic error",
319 "message preserved"
320 );
321 assert!(
322 program.errors[0].loc.is_none(),
323 "loc should be None when absent"
324 );
325 }
326
327 #[test]
328 fn diagnostics_invalid_source_returns_errors_with_location() {
329 let parser = FlowParser::new().expect("failed to create parser");
331
332 let diags = parser.diagnostics("type = ;").expect("runtime error");
334
335 assert!(
337 !diags.is_empty(),
338 "invalid source should produce diagnostics"
339 );
340 let first = &diags[0];
341 assert!(
342 !first.message.is_empty(),
343 "diagnostic should have a message"
344 );
345 let loc = first.loc.as_ref().expect("diagnostic should have location");
346 assert!(loc.start.line >= 1, "start line should be >= 1");
347 }
348}