diff --git a/crates/pyrefly_config/src/config.rs b/crates/pyrefly_config/src/config.rs index 9aab7f946a..2b8081a924 100644 --- a/crates/pyrefly_config/src/config.rs +++ b/crates/pyrefly_config/src/config.rs @@ -52,6 +52,8 @@ use pyrefly_util::telemetry::TelemetrySourceDbRebuildInstanceStats; use pyrefly_util::telemetry::TelemetrySourceDbRebuildStats; use pyrefly_util::watch_pattern::WatchPattern; use serde::Deserialize; +use serde::de::SeqAccess; +use serde::de::Visitor; use serde::Serialize; use starlark_map::small_map::SmallMap; use starlark_map::small_set::SmallSet; @@ -79,15 +81,67 @@ pub static GENERATED_FILE_CONFIG_OVERRIDE: LazyLock< #[derive(Debug, PartialEq, Eq, Deserialize, Serialize, Clone)] pub struct SubConfig { - pub matches: Glob, + #[serde(alias = "match", deserialize_with = "deserialize_sub_config_matches")] + pub matches: Vec, #[serde(flatten)] pub settings: ConfigBase, } impl SubConfig { fn rewrite_with_path_to_config(&mut self, config_root: &Path) { - self.matches = self.matches.clone().from_root(config_root); + self.matches = self + .matches + .clone() + .into_iter() + .map(|glob| glob.from_root(config_root)) + .collect(); } + + fn matches_path(&self, path: &Path) -> bool { + self.matches.iter().any(|glob| glob.matches(path)) + } +} + +fn deserialize_sub_config_matches<'de, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + struct GlobListVisitor; + + impl<'de> Visitor<'de> for GlobListVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a glob string or an array of glob strings") + } + + fn visit_string(self, value: String) -> Result + where + E: serde::de::Error, + { + Ok(vec![Glob::new(value).map_err(E::custom)?]) + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + self.visit_string(value.to_owned()) + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let mut matches = Vec::new(); + while let Some(pattern) = seq.next_element::()? { + matches.push(pattern); + } + Ok(matches) + } + } + + deserializer.deserialize_any(GlobListVisitor) } /// Why a `ConfigFile` was synthesized rather than loaded from a real config @@ -1007,7 +1061,7 @@ impl ConfigFile { path: &Path, ) -> Option { self.sub_configs.iter().find_map(|c| { - if c.matches.matches(path) { + if c.matches_path(path) { return getter(&c.settings); } None @@ -1538,9 +1592,13 @@ impl ConfigFile { for sub_config in &config.sub_configs { if !sub_config.settings.extras.0.is_empty() { let extra_keys = sub_config.settings.extras.0.keys().join(", "); + let matches = sub_config + .matches + .iter() + .map(|glob| glob.to_string()) + .join(", "); errors.push(ConfigError::warn(anyhow!( - "Extra keys found in sub config matching {}: {extra_keys}", - sub_config.matches + "Extra keys found in sub config matching {matches}: {extra_keys}" ))); } } @@ -1729,7 +1787,7 @@ mod tests { }, source_db: Default::default(), sub_configs: vec![SubConfig { - matches: Glob::new("sub/project/**".to_owned()).unwrap(), + matches: vec![Glob::new("sub/project/**".to_owned()).unwrap()], settings: ConfigBase { extras: Default::default(), errors: Some(ErrorDisplayConfig::new(HashMap::from_iter([ @@ -1822,7 +1880,7 @@ mod tests { python_platform = "windows" [[sub_config]] - matches = "abcd" + matches = ["abcd", "efgh"] atliens = 1 "#; @@ -1835,6 +1893,7 @@ mod tests { config.sub_configs[0].settings.extras.0, Table::from_iter([("atliens".to_owned(), Value::Integer(1))]) ); + assert_eq!(config.sub_configs[0].matches.len(), 2); } #[test] @@ -1996,7 +2055,7 @@ mod tests { source_db: Default::default(), build_system: Default::default(), sub_configs: vec![SubConfig { - matches: Glob::new("sub/project/**".to_owned()).unwrap(), + matches: vec![Glob::new("sub/project/**".to_owned()).unwrap()], settings: Default::default(), }], typeshed_path: Some(PathBuf::from(typeshed)), @@ -2026,13 +2085,13 @@ mod tests { python_environment.site_package_path = Some(vec![test_path.join("venv/lib/python1.2.3/site-packages")]); - let sub_config_matches = Glob::new( + let sub_config_matches = vec![Glob::new( test_path .join("sub/project/**") .to_string_lossy() .into_owned(), ) - .unwrap(); + .unwrap()]; config.rewrite_with_path_to_config(&test_path); @@ -2174,7 +2233,10 @@ output-format = "omit-errors" }, sub_configs: vec![ SubConfig { - matches: Glob::new("**/highest/**".to_owned()).unwrap(), + matches: vec![ + Glob::new("**/highest/**".to_owned()).unwrap(), + Glob::new("**/top/**".to_owned()).unwrap(), + ], settings: ConfigBase { replace_imports_with_any: Some(vec![ ModuleWildcard::new("highest").unwrap(), @@ -2184,7 +2246,7 @@ output-format = "omit-errors" }, }, SubConfig { - matches: Glob::new("**/priority*".to_owned()).unwrap(), + matches: vec![Glob::new("**/priority*".to_owned()).unwrap()], settings: ConfigBase { replace_imports_with_any: Some(vec![ ModuleWildcard::new("second").unwrap(), @@ -2203,6 +2265,11 @@ output-format = "omit-errors" ModuleName::from_str("highest") )); + assert!(config.replace_imports_with_any( + Some(Path::new("this/is/top/priority")), + ModuleName::from_str("highest") + )); + // test find fallback match assert!(config.replace_imports_with_any( Some(Path::new("this/is/second/priority")), @@ -2233,7 +2300,7 @@ output-format = "omit-errors" ..Default::default() }, sub_configs: vec![SubConfig { - matches: Glob::new("sub/**".to_owned()).unwrap(), + matches: vec![Glob::new("sub/**".to_owned()).unwrap()], settings: ConfigBase { errors: Some(ErrorDisplayConfig::new(HashMap::from([( ErrorKind::BadReturn, @@ -2282,7 +2349,7 @@ output-format = "omit-errors" ..Default::default() }, sub_configs: vec![SubConfig { - matches: Glob::new("strict/**".to_owned()).unwrap(), + matches: vec![Glob::new("strict/**".to_owned()).unwrap()], settings: ConfigBase { errors: Some(ErrorDisplayConfig::new(HashMap::from([( ErrorKind::BadAssignment, @@ -2314,7 +2381,7 @@ output-format = "omit-errors" ..Default::default() }, sub_configs: vec![SubConfig { - matches: Glob::new("sub/**".to_owned()).unwrap(), + matches: vec![Glob::new("sub/**".to_owned()).unwrap()], settings: ConfigBase { check_unannotated_defs: Some(false), ..Default::default() @@ -2616,7 +2683,7 @@ output-format = "omit-errors" let mut config = ConfigFile { preset: Some(Preset::Legacy), sub_configs: vec![SubConfig { - matches: Glob::new("tests/**".to_owned()).unwrap(), + matches: vec![Glob::new("tests/**".to_owned()).unwrap()], settings: ConfigBase { errors: Some(ErrorDisplayConfig::new(HashMap::from([( ErrorKind::BadOverride, diff --git a/crates/pyrefly_config/src/migration/pyright.rs b/crates/pyrefly_config/src/migration/pyright.rs index 13135da486..497bbac497 100644 --- a/crates/pyrefly_config/src/migration/pyright.rs +++ b/crates/pyrefly_config/src/migration/pyright.rs @@ -40,7 +40,7 @@ impl ExecEnv { ..Default::default() }; Ok(SubConfig { - matches: Glob::new(self.root)?, + matches: vec![Glob::new(self.root)?], settings, }) } diff --git a/crates/pyrefly_config/src/migration/sub_configs.rs b/crates/pyrefly_config/src/migration/sub_configs.rs index a253ee21d0..d72bd1892f 100644 --- a/crates/pyrefly_config/src/migration/sub_configs.rs +++ b/crates/pyrefly_config/src/migration/sub_configs.rs @@ -59,24 +59,21 @@ impl ConfigOptionMigrater for SubConfigs { let sub_configs_vec = sub_configs .into_iter() - .flat_map(|(section, errors)| { + .map(|(section, errors)| -> anyhow::Result { // Split the section headers into individual modules and pair them with the section's error config. // mypy uses module wildcards for its per-module sections, but we use globs. // A simple translation: turn `.` into `/` and `*` into `**`, e.g. `a.*.b` -> `a/**/b`. - section + let matches = section .split(",") .map(|x| x.trim()) .filter(|x| !x.is_empty()) .map(|module| Glob::new(module.replace('.', "/").replace('*', "**"))) - .collect::>() - .into_iter() - .zip(std::iter::repeat(Some(errors))) - }) - .map(|(matches, errors)| -> anyhow::Result { + .collect::, _>>()?; + Ok(SubConfig { - matches: matches?, + matches, settings: ConfigBase { - errors, + errors: Some(errors), ..Default::default() }, }) @@ -140,7 +137,7 @@ mod tests { assert_eq!(pyrefly_cfg.sub_configs.len(), 1); let sub_config = &pyrefly_cfg.sub_configs[0]; - assert_eq!(sub_config.matches.to_string(), "app/models"); + assert_eq!(sub_config.matches[0].to_string(), "app/models"); let errors = sub_config.settings.errors.as_ref().unwrap(); assert_eq!( @@ -174,7 +171,7 @@ mod tests { let models_config = pyrefly_cfg .sub_configs .iter() - .find(|c| c.matches.to_string() == "app/models") + .find(|c| c.matches.iter().any(|glob| glob.to_string() == "app/models")) .unwrap(); let models_errors = models_config.settings.errors.as_ref().unwrap(); assert_eq!( @@ -186,7 +183,7 @@ mod tests { let views_config = pyrefly_cfg .sub_configs .iter() - .find(|c| c.matches.to_string() == "app/views") + .find(|c| c.matches.iter().any(|glob| glob.to_string() == "app/views")) .unwrap(); let views_errors = views_config.settings.errors.as_ref().unwrap(); assert_eq!( @@ -223,31 +220,26 @@ mod tests { let sub_configs = SubConfigs; let _ = sub_configs.migrate_from_mypy(&mypy_cfg, &mut pyrefly_cfg); - assert_eq!(pyrefly_cfg.sub_configs.len(), 2); + assert_eq!(pyrefly_cfg.sub_configs.len(), 1); - // Check that both modules have the same error config + // Check that both modules share the same error config in one sub-config. let models_config = pyrefly_cfg .sub_configs .iter() - .find(|c| c.matches.to_string() == "app/models") + .find(|c| c.matches.iter().any(|glob| glob.to_string() == "app/models")) .unwrap(); - let views_config = pyrefly_cfg - .sub_configs + + assert!(models_config + .matches .iter() - .find(|c| c.matches.to_string() == "app/views") - .unwrap(); + .any(|glob| glob.to_string() == "app/views")); let models_errors = models_config.settings.errors.as_ref().unwrap(); - let views_errors = views_config.settings.errors.as_ref().unwrap(); assert_eq!( models_errors.severity(ErrorKind::MissingAttribute), Severity::Ignore ); - assert_eq!( - views_errors.severity(ErrorKind::MissingAttribute), - Severity::Ignore - ); } #[test] @@ -268,7 +260,7 @@ mod tests { let sub_config = &pyrefly_cfg.sub_configs[0]; // Check that the module wildcard was converted to a glob - assert_eq!(sub_config.matches.to_string(), "app/**/models"); + assert_eq!(sub_config.matches[0].to_string(), "app/**/models"); let errors = sub_config.settings.errors.as_ref().unwrap(); assert_eq!( @@ -325,14 +317,14 @@ mod tests { let src_config = pyrefly_cfg .sub_configs .iter() - .find(|c| c.matches.to_string() == "src") + .find(|c| c.matches.iter().any(|glob| glob.to_string() == "src")) .unwrap(); // Find the sub_config for tests let tests_config = pyrefly_cfg .sub_configs .iter() - .find(|c| c.matches.to_string() == "tests") + .find(|c| c.matches.iter().any(|glob| glob.to_string() == "tests")) .unwrap(); // Verify that the error settings were properly migrated diff --git a/schemas/pyrefly.json b/schemas/pyrefly.json index 8c4b51009b..4deaac350f 100644 --- a/schemas/pyrefly.json +++ b/schemas/pyrefly.json @@ -7,8 +7,18 @@ "$defs": { "errorSeverity": { "oneOf": [ - { "type": "boolean" }, - { "type": "string", "enum": ["ignore", "info", "warn", "error"] } + { + "type": "boolean" + }, + { + "type": "string", + "enum": [ + "ignore", + "info", + "warn", + "error" + ] + } ] }, "configBase": { @@ -21,15 +31,33 @@ "$ref": "#/$defs/errorSeverity" }, "properties": { - "bad-assignment": { "$ref": "#/$defs/errorSeverity" }, - "bad-return": { "$ref": "#/$defs/errorSeverity" }, - "missing-import": { "$ref": "#/$defs/errorSeverity" }, - "invalid-argument": { "$ref": "#/$defs/errorSeverity" }, - "assert-type": { "$ref": "#/$defs/errorSeverity" }, - "bad-override": { "$ref": "#/$defs/errorSeverity" }, - "incompatible-type": { "$ref": "#/$defs/errorSeverity" }, - "unused-import": { "$ref": "#/$defs/errorSeverity" }, - "undefined-variable": { "$ref": "#/$defs/errorSeverity" } + "bad-assignment": { + "$ref": "#/$defs/errorSeverity" + }, + "bad-return": { + "$ref": "#/$defs/errorSeverity" + }, + "missing-import": { + "$ref": "#/$defs/errorSeverity" + }, + "invalid-argument": { + "$ref": "#/$defs/errorSeverity" + }, + "assert-type": { + "$ref": "#/$defs/errorSeverity" + }, + "bad-override": { + "$ref": "#/$defs/errorSeverity" + }, + "incompatible-type": { + "$ref": "#/$defs/errorSeverity" + }, + "unused-import": { + "$ref": "#/$defs/errorSeverity" + }, + "undefined-variable": { + "$ref": "#/$defs/errorSeverity" + } }, "default": {} }, @@ -67,7 +95,11 @@ "untyped-def-behavior": { "description": "How should Pyrefly treat function definitions with no parameter or return type annotations?", "type": "string", - "enum": ["check-and-infer-return-type", "check-and-infer-return-any", "skip-and-infer-return-any"], + "enum": [ + "check-and-infer-return-type", + "check-and-infer-return-any", + "skip-and-infer-return-any" + ], "default": "check-and-infer-return-type" }, "permissive-ignores": { @@ -80,9 +112,19 @@ "type": "array", "items": { "type": "string", - "enum": ["type", "pyrefly", "mypy", "pyright", "pyre", "ty"] + "enum": [ + "type", + "pyrefly", + "mypy", + "pyright", + "pyre", + "ty" + ] }, - "default": ["type", "pyrefly"] + "default": [ + "type", + "pyrefly" + ] }, "tensor-shapes": { "description": "Whether to enable tensor shape checking.", @@ -96,7 +138,10 @@ "recursion-overflow-handler": { "description": "How should Pyrefly handle recursion overflow during type evaluation?", "type": "string", - "enum": ["break-with-placeholder", "panic-with-debug-info"], + "enum": [ + "break-with-placeholder", + "panic-with-debug-info" + ], "default": "break-with-placeholder" } } @@ -112,7 +157,13 @@ "preset": { "description": "Named preset that provides default error severities and behavior settings. User-specified settings override the preset.", "type": "string", - "enum": ["off", "basic", "legacy", "default", "strict"] + "enum": [ + "off", + "basic", + "legacy", + "default", + "strict" + ] }, "project-includes": { "description": "The glob patterns used to describe which files to type check, typically understood as user-space files. This does not specify import resolution priority.", @@ -120,7 +171,10 @@ "items": { "type": "string" }, - "default": ["**/*.py*", "**/*.ipynb"] + "default": [ + "**/*.py*", + "**/*.ipynb" + ] }, "project-excludes": { "description": "The glob patterns used to describe which files to avoid type checking as a way to filter files that match project-includes.", @@ -128,7 +182,12 @@ "items": { "type": "string" }, - "default": ["**/node_modules", "**/__pycache__", "**/venv/**", "**/.[!/.]*/**"] + "default": [ + "**/node_modules", + "**/__pycache__", + "**/venv/**", + "**/.[!/.]*/**" + ] }, "disable-project-excludes-heuristics": { "description": "Disable automatic appending of default project-excludes patterns. When true, you have full control over project-excludes.", @@ -141,7 +200,9 @@ "items": { "type": "string" }, - "default": ["."] + "default": [ + "." + ] }, "disable-search-path-heuristics": { "description": "Disable any search path heuristics/additional search path behavior that Pyrefly will attempt to do for you.", @@ -160,7 +221,16 @@ "description": "The value used with conditions based on type checking against sys.platform values. Common values include 'linux', 'darwin', 'win32', but any valid sys.platform string is accepted.", "type": "string", "default": "linux", - "examples": ["linux", "darwin", "win32", "cygwin", "freebsd", "openbsd", "netbsd", "aix"] + "examples": [ + "linux", + "darwin", + "win32", + "cygwin", + "freebsd", + "openbsd", + "netbsd", + "aix" + ] }, "python-version": { "description": "The Python version to use for type checking. Format: [.[.]]", @@ -210,7 +280,10 @@ "type": { "description": "The type of build system.", "type": "string", - "enum": ["buck", "custom"] + "enum": [ + "buck", + "custom" + ] }, "ignore-if-build-system-missing": { "description": "Whether to silently ignore the build system configuration if the build system is not found.", @@ -249,32 +322,50 @@ "type": "string" } }, - "required": ["type"], + "required": [ + "type" + ], "allOf": [ { "if": { "properties": { - "type": {"const": "buck"} + "type": { + "const": "buck" + } } }, "then": { "not": { - "required": ["command"] + "required": [ + "command" + ] } } }, { "if": { "properties": { - "type": {"const": "custom"} + "type": { + "const": "custom" + } } }, "then": { - "required": ["command"], + "required": [ + "command" + ], "not": { "anyOf": [ - {"required": ["isolation-dir"]}, - {"required": ["extras"]} + { + "required": [ + "isolation-dir" + ] + }, + { + "required": [ + "extras" + ] + } ] } } @@ -291,11 +382,23 @@ }, { "type": "object", - "required": ["matches"], + "required": [ + "matches" + ], "properties": { "matches": { - "description": "A filesystem glob pattern detailing which files the config applies to.", - "type": "string" + "description": "One or more filesystem glob patterns detailing which files the config applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] } } } @@ -307,4 +410,4 @@ } ], "additionalProperties": true -} +} \ No newline at end of file diff --git a/schemas/test-pyproject.toml b/schemas/test-pyproject.toml index bce4e79dfb..8a49fde813 100644 --- a/schemas/test-pyproject.toml +++ b/schemas/test-pyproject.toml @@ -6,42 +6,42 @@ version = "0.1.0" [tool.pyrefly] # Basic project configuration -project-includes = ["src", "tests"] -project-excludes = ["**/node_modules", "**/__pycache__"] disable-project-excludes-heuristics = false +project-excludes = ["**/node_modules", "**/__pycache__"] +project-includes = ["src", "tests"] # Import paths -search-path = ["src", "lib"] disable-search-path-heuristics = false +search-path = ["src", "lib"] site-package-path = ["venv/lib/python3.12/site-packages"] # Python environment +conda-environment = "myenv" +fallback-python-interpreter-name = "python3" +python-interpreter-path = "venv/bin/python3" python-platform = "linux" python-version = "3.12.0" skip-interpreter-query = false -python-interpreter-path = "venv/bin/python3" -conda-environment = "myenv" -fallback-python-interpreter-name = "python3" typeshed-path = "/path/to/typeshed" # Type checking behavior -untyped-def-behavior = "check-and-infer-return-type" -infer-with-first-use = true -ignore-errors-in-generated-code = false disable-type-errors-in-ide = false -tensor-shapes = false -strict-callable-subtyping = false +ignore-errors-in-generated-code = false +infer-with-first-use = true recursion-depth-limit = 100 recursion-overflow-handler = "break-with-placeholder" +strict-callable-subtyping = false +tensor-shapes = false +untyped-def-behavior = "check-and-infer-return-type" # Import handling -replace-imports-with-any = ["sympy.*", "*.series"] ignore-missing-imports = ["numpy.*"] +replace-imports-with-any = ["sympy.*", "*.series"] # Ignore files and directives -use-ignore-files = true -permissive-ignores = false enabled-ignores = ["type", "pyrefly"] +permissive-ignores = false +use-ignore-files = true skip-lsp-config-indexing = false @@ -52,12 +52,12 @@ baseline = "baseline.json" [tool.pyrefly.errors] bad-assignment = false bad-return = true -missing-import = "warn" invalid-argument = "error" +missing-import = "warn" # SubConfig example 1: Test files [[tool.pyrefly.sub-config]] -matches = "**/tests/**" +matches = ["**/tests/**", "**/tests/**/*.py"] untyped-def-behavior = "skip-and-infer-return-any" [tool.pyrefly.sub-config.errors] @@ -65,14 +65,14 @@ assert-type = true # SubConfig example 2: Generated code [[tool.pyrefly.sub-config]] -matches = "**/*_pb2.py" ignore-errors-in-generated-code = true +matches = "**/*_pb2.py" replace-imports-with-any = ["google.protobuf.*"] # Build system configuration [tool.pyrefly.build-system] -type = "buck" -isolation-dir = "pyrefly-iso" extras = ["--config", "client.id=pyrefly"] -search-path-prefix = ["/prefix"] ignore-if-build-system-missing = true +isolation-dir = "pyrefly-iso" +search-path-prefix = ["/prefix"] +type = "buck" diff --git a/schemas/test-pyrefly.toml b/schemas/test-pyrefly.toml index 761f7f1f50..cdf75078ec 100644 --- a/schemas/test-pyrefly.toml +++ b/schemas/test-pyrefly.toml @@ -5,42 +5,42 @@ preset = "legacy" # Basic project configuration -project-includes = ["src", "tests"] -project-excludes = ["**/node_modules", "**/__pycache__"] disable-project-excludes-heuristics = false +project-excludes = ["**/node_modules", "**/__pycache__"] +project-includes = ["src", "tests"] # Import paths -search-path = ["src", "lib"] disable-search-path-heuristics = false +search-path = ["src", "lib"] site-package-path = ["venv/lib/python3.12/site-packages"] # Python environment +conda-environment = "myenv" +fallback-python-interpreter-name = "python3" +python-interpreter-path = "venv/bin/python3" python-platform = "linux" python-version = "3.12.0" skip-interpreter-query = false -python-interpreter-path = "venv/bin/python3" -conda-environment = "myenv" -fallback-python-interpreter-name = "python3" typeshed-path = "/path/to/typeshed" # Type checking behavior -untyped-def-behavior = "check-and-infer-return-type" -infer-with-first-use = true -ignore-errors-in-generated-code = false disable-type-errors-in-ide = false -tensor-shapes = false -strict-callable-subtyping = false +ignore-errors-in-generated-code = false +infer-with-first-use = true recursion-depth-limit = 100 recursion-overflow-handler = "break-with-placeholder" +strict-callable-subtyping = false +tensor-shapes = false +untyped-def-behavior = "check-and-infer-return-type" # Import handling -replace-imports-with-any = ["sympy.*", "*.series"] ignore-missing-imports = ["numpy.*"] +replace-imports-with-any = ["sympy.*", "*.series"] # Ignore files and directives -use-ignore-files = true -permissive-ignores = false enabled-ignores = ["type", "pyrefly"] +permissive-ignores = false +use-ignore-files = true skip-lsp-config-indexing = false @@ -51,12 +51,12 @@ baseline = "baseline.json" [errors] bad-assignment = false bad-return = true -missing-import = "warn" invalid-argument = "error" +missing-import = "warn" # SubConfig example 1: Test files [[sub-config]] -matches = "**/tests/**" +matches = ["sub/project/tests/file.py", "sub/project/tests/**/*.py"] untyped-def-behavior = "skip-and-infer-return-any" [sub-config.errors] @@ -64,14 +64,14 @@ assert-type = true # SubConfig example 2: Generated code [[sub-config]] -matches = "**/*_pb2.py" ignore-errors-in-generated-code = true +matches = "**/*_pb2.py" replace-imports-with-any = ["google.protobuf.*"] # Build system configuration [build-system] -type = "buck" -isolation-dir = "pyrefly-iso" extras = ["--config", "client.id=pyrefly"] -search-path-prefix = ["/prefix"] ignore-if-build-system-missing = true +isolation-dir = "pyrefly-iso" +search-path-prefix = ["/prefix"] +type = "buck" diff --git a/website/docs/configuration.mdx b/website/docs/configuration.mdx index a974fef386..b366029ab6 100644 --- a/website/docs/configuration.mdx +++ b/website/docs/configuration.mdx @@ -1159,7 +1159,7 @@ Examples: filepath glob matching. Only certain config options are allowed to be overridden, and a need to override other configs means you likely need to use a separate config file for your subdirectory. You can have as many SubConfigs as you want in a project, and even multiple separate SubConfigs -that can apply to a given file when the `matches` glob pattern matches. +that can apply to a given file when any `matches` glob pattern matches. #### **SubConfig Allowed Overrides** @@ -1187,15 +1187,15 @@ and would like to see the option supported. A SubConfig has two or more entries: -- a `matches` key, with a [Filesystem Glob](#filesystem-globbing) detailing which files the config - applies to. +- a `matches` key, with a [Filesystem Glob](#filesystem-globbing) or list of globs detailing which + files the config applies to. - at least one of the [SubConfig allowed overrides](#subconfig-allowed-overrides) #### **SubConfig Option Selection** Since you can have more than one SubConfig matching a file, we need to define a resolution order to determine which SubConfig's option should be selected. Pyrefly does this by filtering -SubConfigs whose `matches` does not match the given file, then takes the first non-null +SubConfigs whose `matches` do not match the given file, then takes the first non-null value that can be found in the order the SubConfigs appear in your configuration. If no SubConfigs match, or there are no non-null config options present, then we take @@ -1219,14 +1219,14 @@ invalid-argument = false [[sub-config]] # apply this to `sub/project/tests/file.py` -matches = "sub/project/tests/file.py" +matches = ["sub/project/tests/file.py", "sub/project/tests/**/*.py"] # any unittest imports will by typed as `typing.Any` replace-imports-with-any = ["unittest.*"] [[sub-config]] # apply this config to all files in `sub/project` -matches = "sub/project/**" +matches = ["sub/project/**", "sub/project/tests/**"] # enable `assert-type` errors in `sub/project` [sub-config.errors] @@ -1234,7 +1234,7 @@ assert-type = true [[sub-config]] # apply this config to all files in `sub` -matches = "sub/**" +matches = ["sub/**", "sub/project/**"] # disable `assert-type` errors in `sub` [sub-config.errors] @@ -1242,7 +1242,7 @@ assert-type = false [[sub-config]] # apply this config to all files under `tests` dirs in `sub/` -matches = "sub/**/tests/**" +matches = ["sub/**/tests/**", "sub/tests/**"] # any pytest imports will be typed as `typing.Any` replace-imports-with-any = ["pytest.*"] @@ -1378,14 +1378,14 @@ invalid-argument = false [[sub-config]] # apply this to `sub/project/tests/file.py` -matches = "sub/project/tests/file.py" +matches = ["sub/project/tests/file.py", "sub/project/tests/**/*.py"] # any unittest imports will by typed as `typing.Any` replace-imports-with-any = ["unittest.*"] [[sub-config]] # apply this config to all files in `sub/project` -matches = "sub/project/**" +matches = ["sub/project/**", "sub/project/tests/**"] # enable `assert-type` errors in `sub/project` [sub-config.errors] @@ -1425,7 +1425,7 @@ invalid-argument = false [[tool.pyrefly.sub-config]] # apply this config to all files in `sub/project` -matches = "sub/project/**" +matches = ["sub/project/**", "sub/project/tests/**"] # enable `assert-type` errors in `sub/project` [tool.pyrefly.sub-config.errors] @@ -1433,7 +1433,7 @@ assert-type = true [[tool.pyrefly.sub-config]] # apply this config to all files in `sub` -matches = "sub/**" +matches = ["sub/**", "sub/project/**"] # disable `assert-type` errors in `sub/project` [tool.pyrefly.sub-config.errors]