summaryrefslogtreecommitdiff
path: root/cli/config_file.rs
diff options
context:
space:
mode:
Diffstat (limited to 'cli/config_file.rs')
-rw-r--r--cli/config_file.rs91
1 files changed, 91 insertions, 0 deletions
diff --git a/cli/config_file.rs b/cli/config_file.rs
index f18e56fc1..3ca00b0f4 100644
--- a/cli/config_file.rs
+++ b/cli/config_file.rs
@@ -18,8 +18,10 @@ use deno_core::serde_json::Value;
use deno_core::ModuleSpecifier;
use std::collections::BTreeMap;
use std::collections::HashMap;
+use std::collections::HashSet;
use std::fmt;
use std::path::Path;
+use std::path::PathBuf;
pub(crate) type MaybeImportsResult =
Result<Option<Vec<(ModuleSpecifier, Vec<String>)>>, AnyError>;
@@ -156,6 +158,61 @@ pub const IGNORED_RUNTIME_COMPILER_OPTIONS: &[&str] = &[
"watch",
];
+/// Filenames that Deno will recognize when discovering config.
+const CONFIG_FILE_NAMES: [&str; 2] = ["deno.json", "deno.jsonc"];
+
+pub fn discover(flags: &crate::Flags) -> Result<Option<ConfigFile>, AnyError> {
+ if let Some(config_path) = flags.config_path.as_ref() {
+ Ok(Some(ConfigFile::read(config_path)?))
+ } else {
+ let mut checked = HashSet::new();
+ for f in flags.config_path_args() {
+ if let Some(cf) = discover_from(&f, &mut checked)? {
+ return Ok(Some(cf));
+ }
+ }
+
+ // From CWD walk up to root looking for deno.json or deno.jsonc
+ let cwd = std::env::current_dir()?;
+ discover_from(&cwd, &mut checked)
+ }
+}
+
+pub fn discover_from(
+ start: &Path,
+ checked: &mut HashSet<PathBuf>,
+) -> Result<Option<ConfigFile>, AnyError> {
+ for ancestor in start.ancestors() {
+ if checked.insert(ancestor.to_path_buf()) {
+ for config_filename in CONFIG_FILE_NAMES {
+ let f = ancestor.join(config_filename);
+ match ConfigFile::read(f) {
+ Ok(cf) => {
+ return Ok(Some(cf));
+ }
+ Err(e) => {
+ if let Some(ioerr) = e.downcast_ref::<std::io::Error>() {
+ use std::io::ErrorKind::*;
+ match ioerr.kind() {
+ InvalidInput | PermissionDenied | NotFound => {
+ // ok keep going
+ }
+ _ => {
+ return Err(e); // Unknown error. Stop.
+ }
+ }
+ } else {
+ return Err(e); // Parse error or something else. Stop.
+ }
+ }
+ }
+ }
+ }
+ }
+ // No config file found.
+ Ok(None)
+}
+
/// A function that works like JavaScript's `Object.assign()`.
pub fn json_merge(a: &mut Value, b: &Value) {
match (a, b) {
@@ -823,4 +880,38 @@ mod tests {
}));
assert_eq!(tsconfig1.as_bytes(), tsconfig2.as_bytes());
}
+
+ #[test]
+ fn discover_from_success() {
+ // testdata/fmt/deno.jsonc exists
+ let testdata = test_util::testdata_path();
+ let c_md = testdata.join("fmt/with_config/subdir/c.md");
+ let mut checked = HashSet::new();
+ let config_file = discover_from(&c_md, &mut checked).unwrap().unwrap();
+ assert!(checked.contains(c_md.parent().unwrap()));
+ assert!(!checked.contains(&testdata));
+ let fmt_config = config_file.to_fmt_config().unwrap().unwrap();
+ let expected_exclude = ModuleSpecifier::from_file_path(
+ testdata.join("fmt/with_config/subdir/b.ts"),
+ )
+ .unwrap();
+ assert_eq!(fmt_config.files.exclude, vec![expected_exclude]);
+
+ // Now add all ancestors of testdata to checked.
+ for a in testdata.ancestors() {
+ checked.insert(a.to_path_buf());
+ }
+
+ // If we call discover_from again starting at testdata, we ought to get None.
+ assert!(discover_from(&testdata, &mut checked).unwrap().is_none());
+ }
+
+ #[test]
+ fn discover_from_malformed() {
+ let testdata = test_util::testdata_path();
+ let d = testdata.join("malformed_config/");
+ let mut checked = HashSet::new();
+ let err = discover_from(&d, &mut checked).unwrap_err();
+ assert!(err.to_string().contains("Unable to parse config file"));
+ }
}