1pub mod jvm;
6
7use std::path::Path;
8
9use yog_ids::Format;
10
11use crate::diagnostics;
12
13use crate::project::manifest::SOURCES_DIR as SOURCES;
15
16pub(crate) struct ScriptProject {
21 to_server: bool,
22 files: yog_script_build::FilesBeside,
23 decls: Vec<(String, Vec<yog_script_analyze::ast::Decl>)>,
24}
25
26impl ScriptProject {
27 pub(crate) fn read(root: &Path) -> Self {
29 let to_server =
30 crate::project::manifest::YogToml::read(&root.join(crate::project::manifest::MANIFEST))
31 .is_ok_and(|m| m.to_server);
32 let files = yog_script_build::FilesBeside {
33 base: root.to_path_buf(),
34 };
35 let env = yog_script_analyze::Env {
36 files: Some(&files),
37 to_server,
38 ..Default::default()
39 };
40 let decls = jvm::sources(root)
41 .unwrap_or_default()
42 .into_iter()
43 .map(|(name, text)| {
44 let d = yog_script_analyze::declarations(&name, &text, env);
45 (name, d)
46 })
47 .collect();
48 ScriptProject {
49 to_server,
50 files,
51 decls,
52 }
53 }
54
55 pub(crate) fn check(
58 &self,
59 name: &str,
60 text: &str,
61 content: Option<&[(String, String)]>,
62 ) -> Vec<yog_data::Diagnostic> {
63 let others: Vec<&[yog_script_analyze::ast::Decl]> = self
64 .decls
65 .iter()
66 .filter(|(other, _)| other != name)
67 .map(|(_, d)| d.as_slice())
68 .collect();
69 let project = yog_script_build::Project {
70 to_server: self.to_server,
71 files: Some(&self.files),
72 content,
73 others: &others,
74 ..Default::default()
75 };
76 yog_script_build::check_source_using(name, text, yog_script_build::index::load, project)
77 }
78}
79
80pub(crate) fn check_text(
83 name: &str,
84 text: &str,
85 root: &Path,
86 content: Option<&[(String, String)]>,
87) -> Vec<yog_data::Diagnostic> {
88 ScriptProject::read(root).check(name, text, content)
89}
90
91pub(crate) fn mod_root(file: &Path) -> Option<&Path> {
93 file.ancestors()
94 .find(|dir| dir.join(crate::project::manifest::MANIFEST).is_file())
95}
96
97pub fn check(root: &Path, content: &[(String, String)]) -> Result<(), String> {
101 let dir = root.join(SOURCES);
102 let files: Vec<_> = [Format::Script, Format::Nanites]
103 .into_iter()
104 .flat_map(|format| crate::files::of(&dir, format))
105 .collect();
106 if files.is_empty() {
107 return Ok(());
108 }
109 let project = ScriptProject::read(root);
111 let mut errors = 0;
112 for file in files {
113 let text =
114 std::fs::read_to_string(&file).map_err(|e| format!("{}: {e}", file.display()))?;
115 let name = crate::shown_path(root, &file);
116 let problems = project.check(&name, &text, Some(content));
117 errors += problems.iter().filter(|d| d.is_error()).count();
118 diagnostics::report_all(&problems);
119 for d in &problems {
120 match d.is_error() {
121 true => yog_logging::error!("{d}"),
122 false => yog_logging::warn!("{d}"),
123 }
124 }
125 }
126 match errors {
127 0 => Ok(()),
128 n => Err(format!("{n} error(s) in the mod's Yog Script")),
129 }
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn a_mods_scripts_are_checked() {
138 let root = std::env::temp_dir().join(format!("yog-script-check-{}", std::process::id()));
139 let src = root.join(SOURCES).join("power");
140 std::fs::create_dir_all(&src).unwrap();
141 std::fs::write(src.join("Ok.ys"), "i32 Area(i32 w, i32 h) => w * h;\n").unwrap();
142 assert_eq!(check(&root, &[]), Ok(()));
143 std::fs::write(src.join("Size.ys"), "public record Size(i32 W, i32 H);\n").unwrap();
145 std::fs::write(src.join("Use.ys"), "i32 W(Size s) => s.W;\n").unwrap();
146 assert_eq!(check(&root, &[]), Ok(()));
147 std::fs::write(src.join("Bad.ys"), "void F() {\n break;\n}\n").unwrap();
148 assert!(check(&root, &[]).is_err());
149 std::fs::remove_dir_all(&root).unwrap();
150 }
151}