mirror of
https://github.com/rojo-rbx/rojo.git
synced 2026-04-20 12:45:05 +00:00
Improve error messages
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
|
* Added "EXPERIMENTAL!" label to two-way sync toggle in Rojo's Roblox Studio plugin.
|
||||||
* Fixed `Name` and `Parent` properties being allowed in Rojo projects. ([#413][pr-413])
|
* Fixed `Name` and `Parent` properties being allowed in Rojo projects. ([#413][pr-413])
|
||||||
* Fixed "Open Scripts Externally" feature crashing Studio ([#369][issue-369])
|
* Fixed "Open Scripts Externally" feature crashing Studio ([#369][issue-369])
|
||||||
|
* Improved error messages for misconfigured projects.
|
||||||
|
|
||||||
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
|
[issue-369]: https://github.com/rojo-rbx/rojo/issues/369
|
||||||
[pr-413]: https://github.com/rojo-rbx/rojo/pull/413
|
[pr-413]: https://github.com/rojo-rbx/rojo/pull/413
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{borrow::Cow, collections::HashMap, path::Path};
|
use std::{borrow::Cow, collections::HashMap, path::Path};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::{bail, Context};
|
||||||
use memofs::Vfs;
|
use memofs::Vfs;
|
||||||
use rbx_reflection::ClassTag;
|
use rbx_reflection::ClassTag;
|
||||||
|
|
||||||
@@ -66,11 +66,13 @@ pub fn snapshot_project_node(
|
|||||||
) -> SnapshotInstanceResult {
|
) -> SnapshotInstanceResult {
|
||||||
let project_folder = project_path.parent().unwrap();
|
let project_folder = project_path.parent().unwrap();
|
||||||
|
|
||||||
let name = Cow::Owned(instance_name.to_owned());
|
let class_name_from_project = node
|
||||||
let mut class_name = node
|
|
||||||
.class_name
|
.class_name
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|name| Cow::Owned(name.clone()));
|
.map(|name| Cow::Owned(name.clone()));
|
||||||
|
let mut class_name_from_path = None;
|
||||||
|
|
||||||
|
let name = Cow::Owned(instance_name.to_owned());
|
||||||
let mut properties = HashMap::new();
|
let mut properties = HashMap::new();
|
||||||
let mut children = Vec::new();
|
let mut children = Vec::new();
|
||||||
let mut metadata = InstanceMetadata::default();
|
let mut metadata = InstanceMetadata::default();
|
||||||
@@ -85,24 +87,7 @@ pub fn snapshot_project_node(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(snapshot) = snapshot_from_vfs(context, vfs, &path)? {
|
if let Some(snapshot) = snapshot_from_vfs(context, vfs, &path)? {
|
||||||
// If a class name was already specified, then it'll override the
|
class_name_from_path = Some(snapshot.class_name);
|
||||||
// class name of this snapshot ONLY if it's a Folder.
|
|
||||||
//
|
|
||||||
// This restriction is in place to prevent applying properties to
|
|
||||||
// instances that don't make sense. The primary use-case for using
|
|
||||||
// $className and $path at the same time is to use a directory as a
|
|
||||||
// service in a place file.
|
|
||||||
class_name = match class_name {
|
|
||||||
Some(class_name) => {
|
|
||||||
if snapshot.class_name == "Folder" {
|
|
||||||
Some(class_name)
|
|
||||||
} else {
|
|
||||||
// TODO: Turn this into an error object.
|
|
||||||
panic!("If $className and $path are specified, $path must yield an instance of class Folder");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => Some(snapshot.class_name),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Properties from the snapshot are pulled in unchanged, and
|
// Properties from the snapshot are pulled in unchanged, and
|
||||||
// overridden by properties set on the project node.
|
// overridden by properties set on the project node.
|
||||||
@@ -129,34 +114,66 @@ pub fn snapshot_project_node(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let class_name = class_name
|
let class_name_from_inference = infer_class_name(&name, parent_class);
|
||||||
.or_else(|| {
|
|
||||||
// If className wasn't defined from another source, we may be able
|
|
||||||
// to infer one.
|
|
||||||
|
|
||||||
let parent_class = parent_class?;
|
let class_name = match (
|
||||||
|
class_name_from_project,
|
||||||
|
class_name_from_path,
|
||||||
|
class_name_from_inference,
|
||||||
|
) {
|
||||||
|
// These are the easy, happy paths!
|
||||||
|
(Some(project), None, None) => project,
|
||||||
|
(None, Some(path), None) => path,
|
||||||
|
(None, None, Some(inference)) => inference,
|
||||||
|
|
||||||
if parent_class == "DataModel" {
|
// If the user specifies a class name, but there's an inferred class
|
||||||
// Members of DataModel with names that match known services are
|
// name, we prefer the name listed explicitly by the user.
|
||||||
// probably supposed to be those services.
|
(Some(project), None, Some(_)) => project,
|
||||||
|
|
||||||
let descriptor = rbx_reflection_database::get().classes.get(&name)?;
|
// If the user has a $path pointing to a folder and we're able to infer
|
||||||
|
// a class name, let's use the inferred name. If the path we're pointing
|
||||||
if descriptor.tags.contains(&ClassTag::Service) {
|
// to isn't a folder, though, that's a user error.
|
||||||
return Some(name.clone());
|
(None, Some(path), Some(inference)) => {
|
||||||
}
|
if path == "Folder" {
|
||||||
} else if parent_class == "StarterPlayer" {
|
inference
|
||||||
// StarterPlayer has two special members with their own classes.
|
} else {
|
||||||
|
path
|
||||||
if name == "StarterPlayerScripts" || name == "StarterCharacterScripts" {
|
|
||||||
return Some(name.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
None
|
(Some(project), Some(path), _) => {
|
||||||
})
|
if path == "Folder" {
|
||||||
// TODO: Turn this into an error object.
|
project
|
||||||
.expect("$className or $path must be specified");
|
} else {
|
||||||
|
bail!(
|
||||||
|
"ClassName for Instance \"{}\" was specified in both the project file (as \"{}\") and from the filesystem (as \"{}\").\n\
|
||||||
|
If $className and $path are both set, $path must refer to a Folder.
|
||||||
|
\n\
|
||||||
|
Project path: {}\n\
|
||||||
|
Filesystem path: {}\n",
|
||||||
|
instance_name,
|
||||||
|
project,
|
||||||
|
path,
|
||||||
|
project_path.display(),
|
||||||
|
node.path.as_ref().unwrap().display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(None, None, None) => {
|
||||||
|
bail!(
|
||||||
|
"Instance \"{}\" is missing some required information.\n\
|
||||||
|
One of the following must be true:\n\
|
||||||
|
- $className must be set to the name of a Roblox class\n\
|
||||||
|
- $path must be set to a path of an instance\n\
|
||||||
|
- The instance must be a known service, like ReplicatedStorage\n\
|
||||||
|
\n\
|
||||||
|
Project path: {}",
|
||||||
|
instance_name,
|
||||||
|
project_path.display(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (child_name, child_project_node) in &node.children {
|
for (child_name, child_project_node) in &node.children {
|
||||||
if let Some(child) = snapshot_project_node(
|
if let Some(child) = snapshot_project_node(
|
||||||
@@ -230,6 +247,32 @@ pub fn snapshot_project_node(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn infer_class_name(name: &str, parent_class: Option<&str>) -> Option<Cow<'static, str>> {
|
||||||
|
// If className wasn't defined from another source, we may be able
|
||||||
|
// to infer one.
|
||||||
|
|
||||||
|
let parent_class = parent_class?;
|
||||||
|
|
||||||
|
if parent_class == "DataModel" {
|
||||||
|
// Members of DataModel with names that match known services are
|
||||||
|
// probably supposed to be those services.
|
||||||
|
|
||||||
|
let descriptor = rbx_reflection_database::get().classes.get(name)?;
|
||||||
|
|
||||||
|
if descriptor.tags.contains(&ClassTag::Service) {
|
||||||
|
return Some(Cow::Owned(name.to_owned()));
|
||||||
|
}
|
||||||
|
} else if parent_class == "StarterPlayer" {
|
||||||
|
// StarterPlayer has two special members with their own classes.
|
||||||
|
|
||||||
|
if name == "StarterPlayerScripts" || name == "StarterCharacterScripts" {
|
||||||
|
return Some(Cow::Owned(name.to_owned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
// #[cfg(feature = "broken-tests")]
|
// #[cfg(feature = "broken-tests")]
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"name": "bad_classname_path_conflict",
|
||||||
|
"tree": {
|
||||||
|
"$className": "DataModel",
|
||||||
|
"$path": "foo.txt"
|
||||||
|
}
|
||||||
|
}
|
||||||
0
test-projects/bad_classname_path_conflict/foo.txt
Normal file
0
test-projects/bad_classname_path_conflict/foo.txt
Normal file
4
test-projects/bad_no_classname/default.project.json
Normal file
4
test-projects/bad_no_classname/default.project.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"name": "bad_no_classname",
|
||||||
|
"tree": {}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user