From 4118631c7f8d4c936e8c89d1b9252f4adefc0e46 Mon Sep 17 00:00:00 2001 From: PerishCode Date: Thu, 4 Jun 2026 12:02:09 +0800 Subject: [PATCH] release: prepare 0.3.0 --- Cargo.lock | 2 +- crates/flavor-cli/Cargo.toml | 2 +- crates/flavor-cli/src/plugins/language.rs | 70 +--- .../src/plugins/language/failure.rs | 100 ------ .../src/plugins/language/manifest.rs | 78 +---- crates/flavor-cli/src/plugins/mod.rs | 33 -- .../src/plugins/product/adapters/mod.rs | 39 +-- crates/flavor-cli/src/rules.rs | 57 ---- crates/flavor-cli/src/scan/aggregate.rs | 308 ------------------ crates/flavor-cli/src/scan/mod.rs | 44 +-- crates/flavor-cli/tests/unit/shape.rs | 239 +------------- crates/flavor-plugin-svelte/src/plugin.rs | 33 +- .../src/facts/failure.rs | 168 ---------- .../flavor-plugin-typescript/src/facts/mod.rs | 103 +----- crates/flavor-plugin-typescript/src/lib.rs | 9 +- crates/flavor-plugin-typescript/src/model.rs | 68 ---- crates/flavor-plugin-typescript/src/plugin.rs | 31 +- .../src/state/config.rs | 22 -- .../flavor-plugin-typescript/src/state/mod.rs | 2 +- .../tests/contract.rs | 108 +----- crates/flavor-plugin-vue/src/plugin.rs | 42 +-- grammars/typescript/metadata.json | 2 - 22 files changed, 44 insertions(+), 1516 deletions(-) delete mode 100644 crates/flavor-cli/src/plugins/language/failure.rs delete mode 100644 crates/flavor-cli/src/scan/aggregate.rs delete mode 100644 crates/flavor-plugin-typescript/src/facts/failure.rs diff --git a/Cargo.lock b/Cargo.lock index 3089e2f..119c2e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -78,7 +78,7 @@ checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flavor-cli" -version = "0.2.3" +version = "0.3.0" dependencies = [ "flavor-core", "flavor-grammar", diff --git a/crates/flavor-cli/Cargo.toml b/crates/flavor-cli/Cargo.toml index dadb975..e81e46e 100644 --- a/crates/flavor-cli/Cargo.toml +++ b/crates/flavor-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "flavor-cli" -version = "0.2.3" +version = "0.3.0" edition = "2021" license = "MIT" description = "Personal check-only AST-backed code flavor lint CLI." diff --git a/crates/flavor-cli/src/plugins/language.rs b/crates/flavor-cli/src/plugins/language.rs index acb294f..0dfd38b 100644 --- a/crates/flavor-cli/src/plugins/language.rs +++ b/crates/flavor-cli/src/plugins/language.rs @@ -1,5 +1,4 @@ mod dispatch; -mod failure; mod function; mod manifest; mod name; @@ -14,7 +13,6 @@ pub(crate) use python_manifest::PYTHON_MANIFEST; use std::{collections::BTreeSet, path::Path}; use dispatch::check_dispatch_branches; -use failure::{check_failure_surface, failure_surface_signal}; use function::check_function_bodies; use name::check_name_facts; use shape::check_repeated_token_patterns; @@ -22,14 +20,14 @@ use shape::check_repeated_token_patterns; use crate::{ config::{GuardConfig, NodeKind, RuleSettings}, model::{issue, Issue}, - plugins::{AnalysisContext, FailureSurfaceSignal, PluginOutput, ProductSet, SourceFileScope}, + plugins::{AnalysisContext, PluginOutput, ProductSet, SourceFileScope}, rules::{ - DISPATCH_BRANCH_TOO_LONG, ERROR_FAILURE_SURFACE_MATURITY, NAMING_TOO_MANY_WORDS, - PAYLOAD_ALLOWED_INTRINSICS, PAYLOAD_MAX_BLOCKS, PAYLOAD_MAX_LINES, - PAYLOAD_PRIMITIVE_SOURCES, RUST_PARSE_ERROR, RUST_TESTS_IN_SOURCE, - SHAPE_REPEATED_TOKEN_PATTERN, SVELTE_COMPONENT_TOO_LONG, SVELTE_PARSE_ERROR, - SVELTE_SCRIPT_TOO_LONG, SVELTE_STYLE_TOO_LONG, SVELTE_TEMPLATE_TOO_COMPLEX, - TSX_NO_INTRINSICS, TSX_REQUIRES_PRIMITIVE, TS_PARSE_ERROR, VUE_PARSE_ERROR, + DISPATCH_BRANCH_TOO_LONG, NAMING_TOO_MANY_WORDS, PAYLOAD_ALLOWED_INTRINSICS, + PAYLOAD_MAX_BLOCKS, PAYLOAD_MAX_LINES, PAYLOAD_PRIMITIVE_SOURCES, RUST_PARSE_ERROR, + RUST_TESTS_IN_SOURCE, SHAPE_REPEATED_TOKEN_PATTERN, SVELTE_COMPONENT_TOO_LONG, + SVELTE_PARSE_ERROR, SVELTE_SCRIPT_TOO_LONG, SVELTE_STYLE_TOO_LONG, + SVELTE_TEMPLATE_TOO_COMPLEX, TSX_NO_INTRINSICS, TSX_REQUIRES_PRIMITIVE, TS_PARSE_ERROR, + VUE_PARSE_ERROR, }, }; use flavor_core::{Fact, ProductDiagnostic}; @@ -106,15 +104,8 @@ pub(crate) fn analyze_typescript_source<'a>(context: &AnalysisContext<'a>) -> Pl }; let mut issues = Vec::new(); - let mut failure_surfaces = Vec::new(); - analyze_typescript_products( - context.config, - scope, - &context.products, - &mut issues, - &mut failure_surfaces, - ); - PluginOutput::with_failure_surfaces(issues, failure_surfaces) + analyze_typescript_products(context.config, scope, &context.products, &mut issues); + PluginOutput::issues(issues) } pub(crate) fn analyze_vue_source<'a>(context: &AnalysisContext<'a>) -> PluginOutput<'a> { @@ -132,15 +123,8 @@ pub(crate) fn analyze_vue_source<'a>(context: &AnalysisContext<'a>) -> PluginOut context.products.diagnostics("vue-sfc"), "Vue SFC", ); - let mut failure_surfaces = Vec::new(); - analyze_typescript_products( - context.config, - scope, - &context.products, - &mut issues, - &mut failure_surfaces, - ); - PluginOutput::with_failure_surfaces(issues, failure_surfaces) + analyze_typescript_products(context.config, scope, &context.products, &mut issues); + PluginOutput::issues(issues) } pub(crate) fn analyze_svelte_source<'a>(context: &AnalysisContext<'a>) -> PluginOutput<'a> { @@ -159,15 +143,8 @@ pub(crate) fn analyze_svelte_source<'a>(context: &AnalysisContext<'a>) -> Plugin "Svelte", ); check_svelte_shape(context.config, scope, &context.products, &mut issues); - let mut failure_surfaces = Vec::new(); - analyze_typescript_products( - context.config, - scope, - &context.products, - &mut issues, - &mut failure_surfaces, - ); - PluginOutput::with_failure_surfaces(issues, failure_surfaces) + analyze_typescript_products(context.config, scope, &context.products, &mut issues); + PluginOutput::issues(issues) } fn analyze_typescript_products( @@ -175,7 +152,6 @@ fn analyze_typescript_products( scope: SourceFileScope<'_>, products: &ProductSet, issues: &mut Vec, - failure_surfaces: &mut Vec, ) { let parse_rule = config.rule(scope.relative, NodeKind::File, TS_PARSE_ERROR); push_parse_issues( @@ -198,26 +174,6 @@ fn analyze_typescript_products( products.facts("typescript", "dispatch.branch"), "switch case", ); - - let failure_rule = config.rule( - scope.relative, - NodeKind::File, - ERROR_FAILURE_SURFACE_MATURITY, - ); - check_failure_surface( - issues, - &failure_rule, - scope.path, - products.facts("typescript", "error.raw_failure"), - products.facts("typescript", "error.structured_failure"), - ); - if let Some(signal) = failure_surface_signal( - scope.path, - products.facts("typescript", "error.raw_failure"), - products.facts("typescript", "error.structured_failure"), - ) { - failure_surfaces.push(signal); - } } fn check_rust_tests<'a>( diff --git a/crates/flavor-cli/src/plugins/language/failure.rs b/crates/flavor-cli/src/plugins/language/failure.rs deleted file mode 100644 index b67610f..0000000 --- a/crates/flavor-cli/src/plugins/language/failure.rs +++ /dev/null @@ -1,100 +0,0 @@ -use crate::{ - config::RuleSettings, - model::{issue, Issue}, - plugins::FailureSurfaceSignal, - rules::{PAYLOAD_MAX_RAW_FAILURES, PAYLOAD_MAX_RAW_FAILURE_RATIO_PERCENT}, -}; -use flavor_core::Fact; - -pub(super) fn check_failure_surface<'a>( - issues: &mut Vec, - rule: &RuleSettings, - path: &str, - raw_failures: impl Iterator, - structured_failures: impl Iterator, -) { - if !rule.enabled { - return; - } - - let raw_failures = raw_failures.collect::>(); - let raw_count = raw_failures.len(); - let max_raw = rule.usize(PAYLOAD_MAX_RAW_FAILURES).unwrap_or(4); - if raw_count <= max_raw { - return; - } - - let structured_count = structured_failures.count(); - let total = raw_count + structured_count; - let raw_ratio = raw_count.saturating_mul(100) / total.max(1); - let max_ratio = rule - .usize(PAYLOAD_MAX_RAW_FAILURE_RATIO_PERCENT) - .unwrap_or(60); - if raw_ratio <= max_ratio { - return; - } - - let structured_context = if structured_count == 0 { - "no structured failure surface facts observed".to_string() - } else { - format!("{structured_count} structured failure surface fact(s) observed") - }; - issues.push(issue( - rule.severity, - rule.id, - path, - raw_failures.first().and_then(|fact| fact.line), - format!( - "raw failure construction appears {raw_count} time(s) ({raw_ratio}% of observed failure surface); max raw is {max_raw} and max raw ratio is {max_ratio}%; {structured_context}{}", - raw_failure_examples(&raw_failures) - ), - )); -} - -pub(super) fn failure_surface_signal<'a>( - path: &str, - raw_failures: impl Iterator, - structured_failures: impl Iterator, -) -> Option { - let raw_failures = raw_failures.collect::>(); - let structured_count = structured_failures.count(); - if raw_failures.is_empty() && structured_count == 0 { - return None; - } - Some(FailureSurfaceSignal { - path: path.to_string(), - raw_count: raw_failures.len(), - structured_count, - examples: raw_failures - .iter() - .take(3) - .filter_map(|fact| raw_failure_example(fact)) - .collect(), - }) -} - -fn raw_failure_examples(failures: &[&Fact]) -> String { - let examples = failures - .iter() - .take(3) - .filter_map(|fact| raw_failure_example(fact)) - .collect::>(); - if examples.is_empty() { - String::new() - } else { - format!("; examples: {}", examples.join(", ")) - } -} - -fn raw_failure_example(fact: &Fact) -> Option { - let constructor = fact - .text("constructor") - .filter(|constructor| !constructor.is_empty()); - let callee = fact.text("callee").filter(|callee| !callee.is_empty()); - match (callee, constructor, fact.text("kind")) { - (Some(callee), Some(constructor), _) => Some(format!("{callee}(new {constructor})")), - (_, Some(constructor), _) => Some(format!("new {constructor}")), - (_, _, Some(kind)) => Some(kind.to_string()), - _ => None, - } -} diff --git a/crates/flavor-cli/src/plugins/language/manifest.rs b/crates/flavor-cli/src/plugins/language/manifest.rs index 4039d47..35f2f21 100644 --- a/crates/flavor-cli/src/plugins/language/manifest.rs +++ b/crates/flavor-cli/src/plugins/language/manifest.rs @@ -2,8 +2,7 @@ use crate::{ config::SourceKind, plugins::{FactUse, GrammarUse, PluginManifest, ScopeDecl, ScopeKind}, rules::{ - DISPATCH_BRANCH_TOO_LONG, ERROR_FAILURE_SURFACE_AGGREGATE, ERROR_FAILURE_SURFACE_MATURITY, - NAMING_TOO_MANY_WORDS, RUST_PARSE_ERROR, RUST_TESTS_IN_SOURCE, + DISPATCH_BRANCH_TOO_LONG, NAMING_TOO_MANY_WORDS, RUST_PARSE_ERROR, RUST_TESTS_IN_SOURCE, SHAPE_REPEATED_TOKEN_PATTERN, SVELTE_COMPONENT_TOO_LONG, SVELTE_PARSE_ERROR, SVELTE_SCRIPT_TOO_LONG, SVELTE_STYLE_TOO_LONG, SVELTE_TEMPLATE_TOO_COMPLEX, TSX_NO_INTRINSICS, TSX_REQUIRES_PRIMITIVE, TS_PARSE_ERROR, VUE_PARSE_ERROR, @@ -84,8 +83,6 @@ const RUST_FACTS: &[FactUse] = &[ const TYPESCRIPT_RULES: &[&str] = &[ DISPATCH_BRANCH_TOO_LONG, - ERROR_FAILURE_SURFACE_AGGREGATE, - ERROR_FAILURE_SURFACE_MATURITY, NAMING_TOO_MANY_WORDS, TS_PARSE_ERROR, TSX_NO_INTRINSICS, @@ -128,29 +125,6 @@ const TYPESCRIPT_FACTS: &[FactUse] = &[ "dispatch.branch", &["payload.lines", "span", "line"], ), - fact( - "typescript", - "error.raw_failure", - &[ - "payload.kind", - "payload.mechanism", - "payload.constructor", - "payload.callee", - "span", - "line", - ], - ), - fact( - "typescript", - "error.structured_failure", - &[ - "payload.kind", - "payload.mechanism", - "payload.callee", - "span", - "line", - ], - ), fact( "tsx", "jsx.element", @@ -175,8 +149,6 @@ const TYPESCRIPT_FACTS: &[FactUse] = &[ const VUE_RULES: &[&str] = &[ DISPATCH_BRANCH_TOO_LONG, - ERROR_FAILURE_SURFACE_AGGREGATE, - ERROR_FAILURE_SURFACE_MATURITY, NAMING_TOO_MANY_WORDS, TS_PARSE_ERROR, TSX_NO_INTRINSICS, @@ -229,29 +201,6 @@ const VUE_FACTS: &[FactUse] = &[ "dispatch.branch", &["payload.lines", "span", "line"], ), - fact( - "typescript", - "error.raw_failure", - &[ - "payload.kind", - "payload.mechanism", - "payload.constructor", - "payload.callee", - "span", - "line", - ], - ), - fact( - "typescript", - "error.structured_failure", - &[ - "payload.kind", - "payload.mechanism", - "payload.callee", - "span", - "line", - ], - ), fact( "tsx", "jsx.element", @@ -276,8 +225,6 @@ const VUE_FACTS: &[FactUse] = &[ const SVELTE_RULES: &[&str] = &[ DISPATCH_BRANCH_TOO_LONG, - ERROR_FAILURE_SURFACE_AGGREGATE, - ERROR_FAILURE_SURFACE_MATURITY, NAMING_TOO_MANY_WORDS, SVELTE_COMPONENT_TOO_LONG, SVELTE_PARSE_ERROR, @@ -358,29 +305,6 @@ const SVELTE_FACTS: &[FactUse] = &[ "dispatch.branch", &["payload.lines", "span", "line"], ), - fact( - "typescript", - "error.raw_failure", - &[ - "payload.kind", - "payload.mechanism", - "payload.constructor", - "payload.callee", - "span", - "line", - ], - ), - fact( - "typescript", - "error.structured_failure", - &[ - "payload.kind", - "payload.mechanism", - "payload.callee", - "span", - "line", - ], - ), fact( "tsx", "jsx.element", diff --git a/crates/flavor-cli/src/plugins/mod.rs b/crates/flavor-cli/src/plugins/mod.rs index 7936e92..7b90667 100644 --- a/crates/flavor-cli/src/plugins/mod.rs +++ b/crates/flavor-cli/src/plugins/mod.rs @@ -269,7 +269,6 @@ pub(crate) struct AnalysisContext<'a> { pub(crate) struct PluginOutput<'a> { pub(crate) issues: Vec, pub(crate) child_scopes: Vec>, - pub(crate) failure_surfaces: Vec, } impl<'a> PluginOutput<'a> { @@ -277,28 +276,8 @@ impl<'a> PluginOutput<'a> { Self { issues, child_scopes: Vec::new(), - failure_surfaces: Vec::new(), } } - - pub(crate) fn with_failure_surfaces( - issues: Vec, - failure_surfaces: Vec, - ) -> Self { - Self { - issues, - child_scopes: Vec::new(), - failure_surfaces, - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub(crate) struct FailureSurfaceSignal { - pub(crate) path: String, - pub(crate) raw_count: usize, - pub(crate) structured_count: usize, - pub(crate) examples: Vec, } type PluginAnalyzer = for<'a> fn(&AnalysisContext<'a>) -> PluginOutput<'a>; @@ -358,17 +337,6 @@ impl PluginHost { config: &'a GuardConfig, initial_scope: Scope<'a>, issues: &mut Vec, - ) { - let mut failure_surfaces = Vec::new(); - self.run_scope_with_signals(config, initial_scope, issues, &mut failure_surfaces); - } - - pub(crate) fn run_scope_with_signals<'a>( - &self, - config: &'a GuardConfig, - initial_scope: Scope<'a>, - issues: &mut Vec, - failure_surfaces: &mut Vec, ) { let mut queue = VecDeque::from([initial_scope]); while let Some(scope) = queue.pop_front() { @@ -380,7 +348,6 @@ impl PluginHost { }; let output = (plugin.analyze)(&context); issues.extend(output.issues); - failure_surfaces.extend(output.failure_surfaces); queue.extend(output.child_scopes); } } diff --git a/crates/flavor-cli/src/plugins/product/adapters/mod.rs b/crates/flavor-cli/src/plugins/product/adapters/mod.rs index 25c0002..435bff3 100644 --- a/crates/flavor-cli/src/plugins/product/adapters/mod.rs +++ b/crates/flavor-cli/src/plugins/product/adapters/mod.rs @@ -1,15 +1,14 @@ use flavor_core::GrammarProduct; use flavor_plugin_rust::{RustPluginConfig, RustRepeatedTokenPatternConfig}; -use flavor_plugin_typescript::{TsFailureSurfaceConfig, TsPluginConfig}; +use flavor_plugin_typescript::TsPluginConfig; use crate::{ config::{GuardConfig, NodeKind, SourceKind}, plugins::{PluginManifest, Scope}, rules::{ - ERROR_FAILURE_SURFACE_MATURITY, PAYLOAD_MAX_LINES, PAYLOAD_MAX_REPORTS, PAYLOAD_MAX_TOKENS, - PAYLOAD_MIN_LINES, PAYLOAD_MIN_NODES, PAYLOAD_MIN_OCCURRENCES, PAYLOAD_MIN_TOKENS, - PAYLOAD_MIN_TOTAL_LINES, PAYLOAD_RAW_REJECT_CALLEES, PAYLOAD_STRUCTURED_FACTORIES, - PAYLOAD_STRUCTURED_GUARDS, PAYLOAD_TOKEN_BUCKET_SIZE, SHAPE_REPEATED_TOKEN_PATTERN, + PAYLOAD_MAX_LINES, PAYLOAD_MAX_REPORTS, PAYLOAD_MAX_TOKENS, PAYLOAD_MIN_LINES, + PAYLOAD_MIN_NODES, PAYLOAD_MIN_OCCURRENCES, PAYLOAD_MIN_TOKENS, PAYLOAD_MIN_TOTAL_LINES, + PAYLOAD_TOKEN_BUCKET_SIZE, SHAPE_REPEATED_TOKEN_PATTERN, }, }; @@ -53,18 +52,16 @@ pub(super) fn satisfy( typescript_config(config, &source), &mut products, ), - SourceKind::Vue => flavor_plugin_vue::plugin::satisfy_with_failure_surface( + SourceKind::Vue => flavor_plugin_vue::plugin::satisfy( &|grammar_id| entrypoint(manifest, grammar_id), source.path, source.source, - typescript_config(config, &source).failure_surface, &mut products, ), - SourceKind::Svelte => flavor_plugin_svelte::plugin::satisfy_with_failure_surface( + SourceKind::Svelte => flavor_plugin_svelte::plugin::satisfy( &|grammar_id| entrypoint(manifest, grammar_id), source.path, source.source, - typescript_config(config, &source).failure_surface, &mut products, ), } @@ -73,28 +70,10 @@ pub(super) fn satisfy( } fn typescript_config( - config: &GuardConfig, - source: &crate::plugins::SourceFileScope<'_>, + _config: &GuardConfig, + _source: &crate::plugins::SourceFileScope<'_>, ) -> TsPluginConfig { - let rule = config.rule( - source.relative, - NodeKind::File, - ERROR_FAILURE_SURFACE_MATURITY, - ); - TsPluginConfig { - failure_surface: TsFailureSurfaceConfig { - structured_guards: rule - .string_list(PAYLOAD_STRUCTURED_GUARDS) - .unwrap_or_default(), - structured_factories: rule - .string_list(PAYLOAD_STRUCTURED_FACTORIES) - .unwrap_or_default(), - raw_reject_callees: rule - .string_list(PAYLOAD_RAW_REJECT_CALLEES) - .unwrap_or_else(|| TsFailureSurfaceConfig::default().raw_reject_callees), - }, - ..Default::default() - } + TsPluginConfig::default() } fn rust_config( diff --git a/crates/flavor-cli/src/rules.rs b/crates/flavor-cli/src/rules.rs index f8124e9..20396a5 100644 --- a/crates/flavor-cli/src/rules.rs +++ b/crates/flavor-cli/src/rules.rs @@ -8,8 +8,6 @@ use crate::model::Severity; pub(crate) const NAMING_TOO_MANY_WORDS: &str = "core/naming/too-many-words"; pub(crate) const DISPATCH_BRANCH_TOO_LONG: &str = "core/dispatch/branch-too-long"; pub(crate) const FUNCTION_TOO_LONG: &str = "core/function/too-long"; -pub(crate) const ERROR_FAILURE_SURFACE_AGGREGATE: &str = "core/error/failure-surface-aggregate"; -pub(crate) const ERROR_FAILURE_SURFACE_MATURITY: &str = "core/error/failure-surface-maturity"; pub(crate) const FS_CHILDREN_SHAPE: &str = "core/fs/children-shape"; pub(crate) const FS_FORBIDDEN_EXTENSION: &str = "core/fs/forbidden-extension"; pub(crate) const FS_NAME_SHAPE: &str = "core/fs/name-shape"; @@ -39,33 +37,18 @@ pub(crate) const PAYLOAD_FORBIDDEN: &str = "forbidden"; pub(crate) const PAYLOAD_MAX_REPORTS: &str = "max_reports"; pub(crate) const PAYLOAD_MAX_WORDS: &str = "max_words"; pub(crate) const PAYLOAD_MAX_BRANCH_LINES: &str = "max_branch_lines"; -pub(crate) const PAYLOAD_MAX_AGGREGATE_DIRS: &str = "max_aggregate_dirs"; -pub(crate) const PAYLOAD_MAX_DIRECTORY_RAW_FAILURE_RATIO_PERCENT: &str = - "max_directory_raw_failure_ratio_percent"; -pub(crate) const PAYLOAD_MAX_RAW_FAILURES: &str = "max_raw_failures"; -pub(crate) const PAYLOAD_MAX_RAW_FAILURE_RATIO_PERCENT: &str = "max_raw_failure_ratio_percent"; pub(crate) const PAYLOAD_MAX_BLOCKS: &str = "max_blocks"; pub(crate) const PAYLOAD_MAX_CHILDREN: &str = "max_children"; -pub(crate) const PAYLOAD_MAX_EXAMPLE_FILES: &str = "max_example_files"; pub(crate) const PAYLOAD_MAX_LINES: &str = "max_lines"; pub(crate) const PAYLOAD_MAX_DEPTH: &str = "max_depth"; pub(crate) const PAYLOAD_MAX_TOKENS: &str = "max_tokens"; pub(crate) const PAYLOAD_MIN_LINES: &str = "min_lines"; -pub(crate) const PAYLOAD_MIN_DIRECTORY_RAW_FAILURES: &str = "min_directory_raw_failures"; -pub(crate) const PAYLOAD_MIN_DIRECTORY_RAW_FILES: &str = "min_directory_raw_files"; -pub(crate) const PAYLOAD_MIN_DIRECTORY_SCANNED_FILES: &str = "min_directory_scanned_files"; pub(crate) const PAYLOAD_MIN_NODES: &str = "min_nodes"; pub(crate) const PAYLOAD_MIN_OCCURRENCES: &str = "min_occurrences"; -pub(crate) const PAYLOAD_MIN_RAW_FAILURES: &str = "min_raw_failures"; -pub(crate) const PAYLOAD_MIN_RAW_FILES: &str = "min_raw_files"; -pub(crate) const PAYLOAD_MIN_SCANNED_FILES: &str = "min_scanned_files"; pub(crate) const PAYLOAD_MIN_TOKENS: &str = "min_tokens"; pub(crate) const PAYLOAD_MIN_TOTAL_LINES: &str = "min_total_lines"; pub(crate) const PAYLOAD_PRIMITIVE_SOURCES: &str = "primitive_sources"; -pub(crate) const PAYLOAD_RAW_REJECT_CALLEES: &str = "raw_reject_callees"; pub(crate) const PAYLOAD_REQUIRED: &str = "required"; -pub(crate) const PAYLOAD_STRUCTURED_FACTORIES: &str = "structured_factories"; -pub(crate) const PAYLOAD_STRUCTURED_GUARDS: &str = "structured_guards"; pub(crate) const PAYLOAD_TOKEN_BUCKET_SIZE: &str = "token_bucket_size"; #[derive(Debug, Clone, Copy, Eq, PartialEq, Serialize)] @@ -116,44 +99,6 @@ pub(crate) fn descriptor(rule_id: &str) -> Option { bad_flavor: "A function may be carrying multiple phases, policy choices, or report-building details in one local body.", action_hint: "Look for named phases, model conversion, validation, or rendering boundaries before extracting generic helpers.", }), - ERROR_FAILURE_SURFACE_AGGREGATE => Some(RuleDescriptor { - id: ERROR_FAILURE_SURFACE_AGGREGATE, - target: RuleTarget::Dir, - default_enabled: true, - default_severity: Severity::Warning, - default_payload: payload([ - (PAYLOAD_MIN_SCANNED_FILES, 10), - (PAYLOAD_MIN_RAW_FAILURES, 12), - (PAYLOAD_MIN_RAW_FILES, 4), - (PAYLOAD_MAX_RAW_FAILURE_RATIO_PERCENT, 70), - (PAYLOAD_MIN_DIRECTORY_SCANNED_FILES, 6), - (PAYLOAD_MIN_DIRECTORY_RAW_FAILURES, 8), - (PAYLOAD_MIN_DIRECTORY_RAW_FILES, 3), - (PAYLOAD_MAX_DIRECTORY_RAW_FAILURE_RATIO_PERCENT, 70), - (PAYLOAD_MAX_AGGREGATE_DIRS, 3), - (PAYLOAD_MAX_EXAMPLE_FILES, 5), - ]), - bad_flavor: "Raw failure construction may be distributed widely enough to indicate project-level error modeling pressure.", - action_hint: "Consider defining observable error categories, codes, context propagation, and boundary-specific guard or factory helpers.", - }), - ERROR_FAILURE_SURFACE_MATURITY => Some(RuleDescriptor { - id: ERROR_FAILURE_SURFACE_MATURITY, - target: RuleTarget::File, - default_enabled: true, - default_severity: Severity::Warning, - default_payload: BTreeMap::from([ - (PAYLOAD_MAX_RAW_FAILURES, Value::from(4)), - (PAYLOAD_MAX_RAW_FAILURE_RATIO_PERCENT, Value::from(60)), - ( - PAYLOAD_RAW_REJECT_CALLEES, - Value::Array(vec![Value::from("Promise.reject"), Value::from("reject")]), - ), - (PAYLOAD_STRUCTURED_GUARDS, Value::Array(Vec::new())), - (PAYLOAD_STRUCTURED_FACTORIES, Value::Array(Vec::new())), - ]), - bad_flavor: "Raw failure construction may be becoming the local error protocol instead of flowing through a named error model.", - action_hint: "Consider whether this boundary needs stable error codes, categories, contextual causes, or configured guard/factory helpers before adding more raw failures.", - }), FS_CHILDREN_SHAPE => Some(RuleDescriptor { id: FS_CHILDREN_SHAPE, target: RuleTarget::Dir, @@ -356,8 +301,6 @@ pub(crate) fn known_rule_ids() -> Vec<&'static str> { NAMING_TOO_MANY_WORDS, DISPATCH_BRANCH_TOO_LONG, FUNCTION_TOO_LONG, - ERROR_FAILURE_SURFACE_AGGREGATE, - ERROR_FAILURE_SURFACE_MATURITY, FS_CHILDREN_SHAPE, FS_FORBIDDEN_EXTENSION, FS_NAME_SHAPE, diff --git a/crates/flavor-cli/src/scan/aggregate.rs b/crates/flavor-cli/src/scan/aggregate.rs deleted file mode 100644 index 2bd3287..0000000 --- a/crates/flavor-cli/src/scan/aggregate.rs +++ /dev/null @@ -1,308 +0,0 @@ -use std::{ - collections::BTreeMap, - path::{Path, PathBuf}, -}; - -use crate::{ - config::{GuardConfig, NodeKind, RuleSettings}, - model::{issue, Issue}, - path_match::path_string, - plugins::FailureSurfaceSignal, - rules::{ - ERROR_FAILURE_SURFACE_AGGREGATE, PAYLOAD_MAX_AGGREGATE_DIRS, - PAYLOAD_MAX_DIRECTORY_RAW_FAILURE_RATIO_PERCENT, PAYLOAD_MAX_EXAMPLE_FILES, - PAYLOAD_MAX_RAW_FAILURE_RATIO_PERCENT, PAYLOAD_MIN_DIRECTORY_RAW_FAILURES, - PAYLOAD_MIN_DIRECTORY_RAW_FILES, PAYLOAD_MIN_DIRECTORY_SCANNED_FILES, - PAYLOAD_MIN_RAW_FAILURES, PAYLOAD_MIN_RAW_FILES, PAYLOAD_MIN_SCANNED_FILES, - }, -}; - -const ROOT_DIR: &str = "."; - -pub(super) fn check_failure_surface_aggregate( - config: &GuardConfig, - scanned_files: usize, - scanned_files_by_dir: &BTreeMap, - signals: &[FailureSurfaceSignal], - issues: &mut Vec, -) { - check_root_aggregate(config, scanned_files, signals, issues); - check_directory_aggregates(config, scanned_files_by_dir, signals, issues); -} - -fn check_root_aggregate( - config: &GuardConfig, - scanned_files: usize, - signals: &[FailureSurfaceSignal], - issues: &mut Vec, -) { - let rule = config.rule( - Path::new(ROOT_DIR), - NodeKind::Dir, - ERROR_FAILURE_SURFACE_AGGREGATE, - ); - let signals = signals.iter().collect::>(); - if let Some(finding) = aggregate_finding( - &rule, - ROOT_DIR, - scanned_files, - &signals, - root_thresholds(&rule), - ) { - issues.push(finding.issue); - } -} - -fn check_directory_aggregates( - config: &GuardConfig, - scanned_files_by_dir: &BTreeMap, - signals: &[FailureSurfaceSignal], - issues: &mut Vec, -) { - let root_rule = config.rule( - Path::new(ROOT_DIR), - NodeKind::Dir, - ERROR_FAILURE_SURFACE_AGGREGATE, - ); - let max_dirs = root_rule.usize(PAYLOAD_MAX_AGGREGATE_DIRS).unwrap_or(3); - if max_dirs == 0 { - return; - } - - let mut candidates = Vec::new(); - for (dir, signals) in signals_by_directory(signals) { - if is_root_dir(&dir) { - continue; - } - let rule = config.rule(&dir, NodeKind::Dir, ERROR_FAILURE_SURFACE_AGGREGATE); - let scanned_files = scanned_files_by_dir.get(&dir).copied().unwrap_or_default(); - if let Some(finding) = aggregate_finding( - &rule, - path_string(&dir), - scanned_files, - &signals, - directory_thresholds(&rule), - ) { - candidates.push(AggregateCandidate { - path: dir, - raw_sites: finding.raw_sites, - issue: finding.issue, - }); - } - } - - candidates.sort_by(|left, right| { - path_depth(&right.path) - .cmp(&path_depth(&left.path)) - .then_with(|| right.raw_sites.cmp(&left.raw_sites)) - .then_with(|| path_string(&left.path).cmp(&path_string(&right.path))) - }); - - let mut emitted_dirs: Vec = Vec::new(); - for candidate in candidates { - if emitted_dirs - .iter() - .any(|emitted| is_ancestor_dir(&candidate.path, emitted)) - { - continue; - } - issues.push(candidate.issue); - emitted_dirs.push(candidate.path); - if emitted_dirs.len() >= max_dirs { - break; - } - } -} - -#[derive(Debug)] -struct AggregateCandidate { - path: PathBuf, - raw_sites: usize, - issue: Issue, -} - -#[derive(Debug)] -struct AggregateFinding { - raw_sites: usize, - issue: Issue, -} - -#[derive(Debug, Clone, Copy)] -struct AggregateThresholds { - min_scanned_files: usize, - min_raw_sites: usize, - min_raw_files: usize, - max_raw_ratio: usize, - max_example_files: usize, -} - -fn aggregate_finding( - rule: &RuleSettings, - path: impl Into, - scanned_files: usize, - signals: &[&FailureSurfaceSignal], - thresholds: AggregateThresholds, -) -> Option { - if !rule.enabled || scanned_files < thresholds.min_scanned_files { - return None; - } - - let raw_sites = signals.iter().map(|signal| signal.raw_count).sum::(); - let raw_files = signals.iter().filter(|signal| signal.raw_count > 0).count(); - let structured_sites = signals - .iter() - .map(|signal| signal.structured_count) - .sum::(); - - if raw_sites < thresholds.min_raw_sites || raw_files < thresholds.min_raw_files { - return None; - } - - let total_sites = raw_sites + structured_sites; - let raw_ratio = raw_sites.saturating_mul(100) / total_sites.max(1); - if raw_ratio <= thresholds.max_raw_ratio { - return None; - } - - Some(AggregateFinding { - raw_sites, - issue: issue( - rule.severity, - rule.id, - path, - None, - aggregate_message( - scanned_files, - raw_sites, - raw_files, - structured_sites, - raw_ratio, - thresholds.max_raw_ratio, - top_raw_files(signals, thresholds.max_example_files), - ), - ), - }) -} - -fn root_thresholds(rule: &RuleSettings) -> AggregateThresholds { - AggregateThresholds { - min_scanned_files: rule.usize(PAYLOAD_MIN_SCANNED_FILES).unwrap_or(10), - min_raw_sites: rule.usize(PAYLOAD_MIN_RAW_FAILURES).unwrap_or(12), - min_raw_files: rule.usize(PAYLOAD_MIN_RAW_FILES).unwrap_or(4), - max_raw_ratio: rule - .usize(PAYLOAD_MAX_RAW_FAILURE_RATIO_PERCENT) - .unwrap_or(70), - max_example_files: rule.usize(PAYLOAD_MAX_EXAMPLE_FILES).unwrap_or(5), - } -} - -fn directory_thresholds(rule: &RuleSettings) -> AggregateThresholds { - AggregateThresholds { - min_scanned_files: rule.usize(PAYLOAD_MIN_DIRECTORY_SCANNED_FILES).unwrap_or(6), - min_raw_sites: rule.usize(PAYLOAD_MIN_DIRECTORY_RAW_FAILURES).unwrap_or(8), - min_raw_files: rule.usize(PAYLOAD_MIN_DIRECTORY_RAW_FILES).unwrap_or(3), - max_raw_ratio: rule - .usize(PAYLOAD_MAX_DIRECTORY_RAW_FAILURE_RATIO_PERCENT) - .unwrap_or(70), - max_example_files: rule.usize(PAYLOAD_MAX_EXAMPLE_FILES).unwrap_or(5), - } -} - -fn aggregate_message( - scanned_files: usize, - raw_sites: usize, - raw_files: usize, - structured_sites: usize, - raw_ratio: usize, - max_ratio: usize, - top_files: Vec, -) -> String { - let top = if top_files.is_empty() { - String::new() - } else { - format!("; top files: {}", top_files.join(", ")) - }; - format!( - "raw failure construction appears {raw_sites} time(s) across {raw_files} file(s) in {scanned_files} scanned file(s) ({raw_ratio}% of observed failure surface); structured failure surface appears {structured_sites} time(s); max raw ratio is {max_ratio}%{top}" - ) -} - -fn top_raw_files(signals: &[&FailureSurfaceSignal], max_files: usize) -> Vec { - let mut signals = signals - .iter() - .filter(|signal| signal.raw_count > 0) - .copied() - .collect::>(); - signals.sort_by(|left, right| { - right - .raw_count - .cmp(&left.raw_count) - .then_with(|| left.path.cmp(&right.path)) - }); - signals - .into_iter() - .take(max_files) - .map(file_summary) - .collect() -} - -fn file_summary(signal: &FailureSurfaceSignal) -> String { - if signal.examples.is_empty() { - format!("{} ({})", signal.path, signal.raw_count) - } else { - format!( - "{} ({}, e.g. {})", - signal.path, - signal.raw_count, - signal.examples.join(", ") - ) - } -} - -fn signals_by_directory( - signals: &[FailureSurfaceSignal], -) -> BTreeMap> { - let mut grouped: BTreeMap> = BTreeMap::new(); - for signal in signals { - for dir in ancestor_dirs(Path::new(&signal.path)) { - grouped.entry(dir).or_default().push(signal); - } - } - grouped -} - -fn ancestor_dirs(path: &Path) -> Vec { - let mut dirs = Vec::new(); - let mut current = normalize_dir(path.parent().unwrap_or_else(|| Path::new(ROOT_DIR))); - loop { - dirs.push(current.clone()); - if is_root_dir(¤t) { - break; - } - current = normalize_dir(current.parent().unwrap_or_else(|| Path::new(ROOT_DIR))); - } - dirs -} - -fn normalize_dir(path: &Path) -> PathBuf { - if path.as_os_str().is_empty() { - PathBuf::from(ROOT_DIR) - } else { - path.to_path_buf() - } -} - -fn is_root_dir(path: &Path) -> bool { - path.as_os_str() == ROOT_DIR -} - -fn is_ancestor_dir(ancestor: &Path, descendant: &Path) -> bool { - !is_root_dir(ancestor) && ancestor != descendant && descendant.starts_with(ancestor) -} - -fn path_depth(path: &Path) -> usize { - path.components() - .filter_map(|component| component.as_os_str().to_str()) - .filter(|segment| !segment.is_empty() && *segment != ROOT_DIR) - .count() -} diff --git a/crates/flavor-cli/src/scan/mod.rs b/crates/flavor-cli/src/scan/mod.rs index ef62461..832181b 100644 --- a/crates/flavor-cli/src/scan/mod.rs +++ b/crates/flavor-cli/src/scan/mod.rs @@ -1,5 +1,3 @@ -mod aggregate; - use std::{ collections::{BTreeMap, BTreeSet}, fs, @@ -10,13 +8,11 @@ use rayon::prelude::*; use tracing::debug; use walkdir::WalkDir; -use aggregate::check_failure_surface_aggregate; - use crate::{ config::{source_file_kind, GuardConfig, SourceKind}, model::{Issue, ScanStats}, path_match::{path_string, relative_path}, - plugins::{FailureSurfaceSignal, PluginHost, Scope}, + plugins::{PluginHost, Scope}, }; const GENERATED_MARKERS: &[&str] = &[ @@ -55,14 +51,6 @@ pub(crate) fn run_scan(config: &GuardConfig) -> Result { &mut plan.issues, ); } - check_failure_surface_aggregate( - config, - plan.stats.scanned_files, - &plan.scanned_source_dirs, - &plan.failure_surfaces, - &mut plan.issues, - ); - plan.issues.sort_by(|left, right| { (left.path.as_str(), left.line.unwrap_or(0), left.rule).cmp(&( right.path.as_str(), @@ -151,8 +139,6 @@ struct ScanPlan { direct_children: BTreeMap>, source_files: Vec, source_kinds: BTreeSet, - scanned_source_dirs: BTreeMap, - failure_surfaces: Vec, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -168,7 +154,6 @@ struct SourceFileResult { relative: PathBuf, status: SourceFileStatus, issues: Vec, - failure_surfaces: Vec, } #[derive(Debug, Clone, Copy, Eq, PartialEq)] @@ -251,24 +236,20 @@ fn analyze_source_file( relative: job.relative.clone(), status: SourceFileStatus::Generated, issues: Vec::new(), - failure_surfaces: Vec::new(), }); } debug!(path = %job.relative.display(), kind = ?job.kind, "scanning source file"); let mut issues = Vec::new(); - let mut failure_surfaces = Vec::new(); - host.run_scope_with_signals( + host.run_scope( config, Scope::source_file(&job.relative, &job.path, &source, job.kind), &mut issues, - &mut failure_surfaces, ); Ok(SourceFileResult { relative: job.relative.clone(), status: SourceFileStatus::Scanned, issues, - failure_surfaces, }) } @@ -281,9 +262,7 @@ fn reduce_source_results(results: Vec, plan: &mut ScanPlan) { SourceFileStatus::Scanned => { plan.stats.scanned_files += 1; add_child_count(&mut plan.child_counts, &result.relative); - add_source_dir_counts(&mut plan.scanned_source_dirs, &result.relative); plan.issues.extend(result.issues); - plan.failure_surfaces.extend(result.failure_surfaces); } } } @@ -301,25 +280,6 @@ fn add_child_count(child_counts: &mut BTreeMap, relative: &Path) *child_counts.entry(parent.to_path_buf()).or_default() += 1; } -fn add_source_dir_counts(counts: &mut BTreeMap, relative: &Path) { - let mut current = normalize_dir(relative.parent().unwrap_or_else(|| Path::new("."))); - loop { - *counts.entry(current.clone()).or_default() += 1; - if current.as_os_str() == "." { - break; - } - current = normalize_dir(current.parent().unwrap_or_else(|| Path::new("."))); - } -} - -fn normalize_dir(path: &Path) -> PathBuf { - if path.as_os_str().is_empty() { - PathBuf::from(".") - } else { - path.to_path_buf() - } -} - fn track_direct_child(child_map: &mut BTreeMap>, relative: &Path) { let Some(parent) = relative.parent() else { return; diff --git a/crates/flavor-cli/tests/unit/shape.rs b/crates/flavor-cli/tests/unit/shape.rs index 288a7d3..2134f56 100644 --- a/crates/flavor-cli/tests/unit/shape.rs +++ b/crates/flavor-cli/tests/unit/shape.rs @@ -3,14 +3,7 @@ use std::{ path::{Path, PathBuf}, }; -use crate::{ - config::GuardConfig, - rules::{ - ERROR_FAILURE_SURFACE_AGGREGATE, ERROR_FAILURE_SURFACE_MATURITY, - SHAPE_REPEATED_TOKEN_PATTERN, - }, - scan::run_scan, -}; +use crate::{config::GuardConfig, rules::SHAPE_REPEATED_TOKEN_PATTERN, scan::run_scan}; #[test] fn helper_keys_are_opaque() { @@ -100,236 +93,6 @@ fn lower_thresholds_report() { let _ = fs::remove_dir_all(root); } -#[test] -fn warns_failure_surface() { - let root = test_root("failure-surface"); - let source_dir = root.join("tools/demo/src"); - fs::create_dir_all(&source_dir).unwrap(); - fs::write( - source_dir.join("errors.ts"), - r#"function one() { throw new Error("one"); } -function two() { throw new Error("two"); } -function three() { throw new Error("three"); } -function four() { throw new Error("four"); } -function five() { throw new Error("five"); } -"#, - ) - .unwrap(); - - let config = test_config(&root, "tools/*/src/**"); - let result = run_scan(&config).unwrap(); - - let issue = result - .issues - .iter() - .find(|issue| issue.rule == ERROR_FAILURE_SURFACE_MATURITY) - .expect("failure surface warning"); - assert!( - issue.message.contains("raw failure construction appears 5"), - "unexpected message: {}", - issue.message - ); - - let _ = fs::remove_dir_all(root); -} - -#[test] -fn structured_surface_ratio() { - let root = test_root("failure-surface-structured"); - let source_dir = root.join("tools/demo/src"); - fs::create_dir_all(&source_dir).unwrap(); - fs::write( - source_dir.join("errors.ts"), - r#"function load(value: string) { - ensure(value); - ensure(value); - ensure(value); - ensure(value); - ensure(value); - throw new Error("one"); - throw new Error("two"); - throw new Error("three"); - throw new Error("four"); - throw new Error("five"); -} -"#, - ) - .unwrap(); - - let config = config_from( - &root, - r#"{ - "scan": { "include": ["tools/*/src/**"] }, - "overrides": [ - { - "match": "tools/demo/src/**/*.ts", - "kind": "file", - "rules": { - "core/error/failure-surface-maturity": { - "payload": { - "max_raw_failures": 4, - "max_raw_failure_ratio_percent": 60, - "structured_guards": ["ensure"] - } - } - } - } - ] - }"#, - ); - let result = run_scan(&config).unwrap(); - - assert!( - !result - .issues - .iter() - .any(|issue| issue.rule == ERROR_FAILURE_SURFACE_MATURITY), - "structured guard facts should keep raw failure ratio below the warning line: {:?}", - result.issues - ); - - let _ = fs::remove_dir_all(root); -} - -#[test] -fn warns_reject_surface() { - let root = test_root("failure-surface-reject"); - let source_dir = root.join("tools/demo/src"); - fs::create_dir_all(&source_dir).unwrap(); - fs::write( - source_dir.join("async.ts"), - r#"function one() { return Promise.reject(new Error("one")); } -function two() { return Promise.reject(new TypeError("two")); } -function three(reject: (error: Error) => void) { reject(new RangeError("three")); } -function four() { return Promise.reject(new Error("four")); } -function five() { return Promise.reject(new Error("five")); } -"#, - ) - .unwrap(); - - let config = test_config(&root, "tools/*/src/**"); - let result = run_scan(&config).unwrap(); - - assert!( - result - .issues - .iter() - .any(|issue| issue.rule == ERROR_FAILURE_SURFACE_MATURITY - && issue.message.contains("raw failure construction appears 5")), - "expected reject calls to count as raw failure surface: {:?}", - result.issues - ); - - let _ = fs::remove_dir_all(root); -} - -#[test] -fn warns_failure_aggregate() { - let root = test_root("failure-surface-aggregate"); - let source_dir = root.join("tools/demo/src"); - fs::create_dir_all(&source_dir).unwrap(); - for index in 0..4 { - fs::write( - source_dir.join(format!("raw_{index}.ts")), - format!( - "function a{index}() {{ throw new Error('a'); }}\n\ - function b{index}() {{ throw new TypeError('b'); }}\n\ - function c{index}() {{ return Promise.reject(new RangeError('c')); }}\n" - ), - ) - .unwrap(); - } - for index in 0..6 { - fs::write( - source_dir.join(format!("clean_{index}.ts")), - format!("function ok{index}() {{ return {index}; }}\n"), - ) - .unwrap(); - } - - let config = test_config(&root, "tools/*/src/**"); - let result = run_scan(&config).unwrap(); - - assert!( - result.issues.iter().any(|issue| { - issue.rule == ERROR_FAILURE_SURFACE_AGGREGATE - && issue.path == "." - && issue.message.contains("12 time(s) across 4 file(s)") - }), - "expected repo-level failure surface aggregate warning: {:?}", - result.issues - ); - assert!( - !result - .issues - .iter() - .any(|issue| issue.rule == ERROR_FAILURE_SURFACE_MATURITY), - "aggregate fixture should not need per-file warnings: {:?}", - result.issues - ); - - let _ = fs::remove_dir_all(root); -} - -#[test] -fn warns_deepest_failure_aggregate() { - let root = test_root("failure-surface-dir-aggregate"); - let source_dir = root.join("tools/demo/src/hot"); - fs::create_dir_all(&source_dir).unwrap(); - for index in 0..3 { - fs::write( - source_dir.join(format!("raw_{index}.ts")), - format!( - "function a{index}() {{ throw new Error('a'); }}\n\ - function b{index}() {{ throw new TypeError('b'); }}\n\ - function c{index}() {{ return Promise.reject(new RangeError('c')); }}\n" - ), - ) - .unwrap(); - } - for index in 0..3 { - fs::write( - source_dir.join(format!("clean_{index}.ts")), - format!("function ok{index}() {{ return {index}; }}\n"), - ) - .unwrap(); - } - - let config = test_config(&root, "tools/*/src/**"); - let result = run_scan(&config).unwrap(); - let aggregate_issues = result - .issues - .iter() - .filter(|issue| issue.rule == ERROR_FAILURE_SURFACE_AGGREGATE) - .collect::>(); - - assert_eq!( - aggregate_issues.len(), - 1, - "expected deepest directory aggregate only: {:?}", - result.issues - ); - let issue = aggregate_issues[0]; - assert_eq!(issue.path, "tools/demo/src/hot"); - assert!( - issue - .message - .contains("9 time(s) across 3 file(s) in 6 scanned file(s)"), - "unexpected message: {}", - issue.message - ); - assert!( - !result - .issues - .iter() - .any(|issue| issue.rule == ERROR_FAILURE_SURFACE_MATURITY), - "directory aggregate fixture should stay below per-file thresholds: {:?}", - result.issues - ); - - let _ = fs::remove_dir_all(root); -} - fn repeated_handlers(count: usize) -> String { let mut source = String::new(); for index in 0..count { diff --git a/crates/flavor-plugin-svelte/src/plugin.rs b/crates/flavor-plugin-svelte/src/plugin.rs index fe4ceaa..4b224f9 100644 --- a/crates/flavor-plugin-svelte/src/plugin.rs +++ b/crates/flavor-plugin-svelte/src/plugin.rs @@ -1,9 +1,7 @@ use std::collections::BTreeMap; use flavor_core::{diagnostics, product, FactPayload, GrammarProduct, PendingFact, SourceText}; -use flavor_plugin_typescript::{ - plugin as typescript_plugin, SourceMode, TsFailureSurfaceConfig, TsPluginConfig, -}; +use flavor_plugin_typescript::{plugin as typescript_plugin, SourceMode, TsPluginConfig}; use flavor_shared::product as shared_product; use crate::{ @@ -18,24 +16,6 @@ pub fn prewarm() { pub fn satisfy(entrypoint: &F, path: &str, source: &str, products: &mut Vec) where F: Fn(&str) -> Option<&'static str>, -{ - satisfy_with_failure_surface( - entrypoint, - path, - source, - TsFailureSurfaceConfig::default(), - products, - ); -} - -pub fn satisfy_with_failure_surface( - entrypoint: &F, - path: &str, - source: &str, - failure_surface: TsFailureSurfaceConfig, - products: &mut Vec, -) where - F: Fn(&str) -> Option<&'static str>, { let source_text = SourceText::new(path, source); let line_index = source_text.line_index(); @@ -77,14 +57,7 @@ pub fn satisfy_with_failure_surface( .into_iter() .chain(output.descriptor.script.clone()) { - push_embedded_script( - entrypoint, - path, - block, - &failure_surface, - &mut facts, - products, - ); + push_embedded_script(entrypoint, path, block, &mut facts, products); } product(products, "svelte", svelte_entrypoint, diagnostics, facts); } @@ -117,7 +90,6 @@ fn push_embedded_script( entrypoint: &F, path: &str, block: SvelteBlock, - failure_surface: &TsFailureSurfaceConfig, facts: &mut Vec, products: &mut Vec, ) where @@ -143,7 +115,6 @@ fn push_embedded_script( } else { SourceMode::TypeScript }, - failure_surface: failure_surface.clone(), ..Default::default() }; typescript_plugin::satisfy_script_with_config( diff --git a/crates/flavor-plugin-typescript/src/facts/failure.rs b/crates/flavor-plugin-typescript/src/facts/failure.rs deleted file mode 100644 index 09977ff..0000000 --- a/crates/flavor-plugin-typescript/src/facts/failure.rs +++ /dev/null @@ -1,168 +0,0 @@ -use flavor_grammar::{GrammarNode, TokenTextRun}; - -use crate::{ - internal::grammar::{self as kind}, - model::TsStructuredFailureKind, - state::TsFailureSurfaceConfig, -}; - -type TsNode = GrammarNode; - -pub(super) const RAW_FAILURE_LITERAL_TOKENS: &[&str] = &["STRING_LITERAL", "TEMPLATE_LITERAL"]; - -const BUILTIN_ERROR_CONSTRUCTORS: &[&str] = &[ - "Error", - "EvalError", - "RangeError", - "ReferenceError", - "SyntaxError", - "TypeError", - "URIError", -]; -const CALLEE_JOIN_TOKENS: &[&str] = &["DOT", "QUESTION_DOT"]; -const CALLEE_PART_TOKENS: &[&str] = &["IDENTIFIER", "KEYWORD_SUPER", "KEYWORD_THIS"]; - -pub(super) fn constructor_name(node: &TsNode) -> Option { - if let Some(member) = node.child(kind::MEMBER_EXPRESSION) { - return member.token_run_text(TokenTextRun::new(CALLEE_PART_TOKENS, CALLEE_JOIN_TOKENS)); - } - node.child_token_text_any(&["IDENTIFIER"]) -} - -pub(super) fn throw_new_expression(expression: &TsNode) -> Option { - expression.child(kind::NEW_EXPRESSION).or_else(|| { - expression - .child(kind::PARENTHESIZED_EXPRESSION) - .and_then(|node| node.child(kind::NEW_EXPRESSION)) - }) -} - -pub(super) fn callee_name(node: &TsNode) -> Option { - if let Some(member) = node.child(kind::MEMBER_EXPRESSION) { - return member.token_run_text(TokenTextRun::new(CALLEE_PART_TOKENS, CALLEE_JOIN_TOKENS)); - } - node.head_token_text_any(CALLEE_PART_TOKENS) -} - -pub(super) fn raw_error_argument_constructor(node: &TsNode) -> Option { - let arguments = node.child(kind::PARENTHESIZED_EXPRESSION)?; - let tokens = token_views(&arguments); - let mut after_outer_open = false; - let mut depth = 0usize; - let mut argument_start = false; - - for (index, token) in tokens.iter().enumerate() { - if !after_outer_open { - if token.kind == kind::OPEN_PAREN { - after_outer_open = true; - argument_start = true; - } - continue; - } - - if depth == 0 && token.kind == kind::CLOSE_PAREN { - break; - } - if depth == 0 && token.kind == kind::COMMA { - argument_start = true; - continue; - } - if depth == 0 && argument_start && token.kind == kind::KEYWORD_NEW { - if let Some(constructor) = constructor_from_tokens(&tokens[index + 1..]) { - if is_builtin_error_constructor(&constructor) { - return Some(constructor); - } - } - } - - update_depth(&token.kind, &mut depth); - if depth == 0 && token.kind != kind::COMMA { - argument_start = false; - } - } - - None -} - -pub(super) fn structured_failure_kind( - callee: &str, - config: &TsFailureSurfaceConfig, -) -> Option { - if matches_configured_callee(callee, &config.structured_guards) { - Some(TsStructuredFailureKind::Guard) - } else if matches_configured_callee(callee, &config.structured_factories) { - Some(TsStructuredFailureKind::Factory) - } else { - None - } -} - -pub(super) fn matches_configured_callee(callee: &str, configured: &[String]) -> bool { - configured - .iter() - .map(|value| value.trim()) - .any(|value| !value.is_empty() && callee_matches(callee, value)) -} - -pub(super) fn is_builtin_error_constructor(name: &str) -> bool { - let leaf = name.rsplit_once('.').map_or(name, |(_, leaf)| leaf); - BUILTIN_ERROR_CONSTRUCTORS.contains(&leaf) -} - -#[derive(Debug, Clone)] -struct TokenView { - kind: String, - text: String, -} - -fn token_views(node: &TsNode) -> Vec { - node.tokens() - .filter_map(|token| { - Some(TokenView { - kind: token.kind_name()?.to_string(), - text: token.text().to_string(), - }) - }) - .collect() -} - -fn constructor_from_tokens(tokens: &[TokenView]) -> Option { - let first = tokens.first()?; - if first.kind != kind::IDENTIFIER { - return None; - } - let mut name = first.text.clone(); - let mut index = 1usize; - while tokens - .get(index) - .is_some_and(|token| token.kind == kind::DOT) - { - let Some(part) = tokens.get(index + 1) else { - break; - }; - if part.kind != kind::IDENTIFIER { - break; - } - name.push('.'); - name.push_str(&part.text); - index += 2; - } - Some(name) -} - -fn callee_matches(callee: &str, configured: &str) -> bool { - callee == configured - || callee - .strip_prefix(configured) - .is_some_and(|tail| tail.starts_with('.') || tail.starts_with("?.")) -} - -fn update_depth(kind: &str, depth: &mut usize) { - match kind { - kind::OPEN_PAREN | kind::OPEN_BRACKET | kind::OPEN_BRACE => *depth += 1, - kind::CLOSE_PAREN | kind::CLOSE_BRACKET | kind::CLOSE_BRACE => { - *depth = depth.saturating_sub(1); - } - _ => {} - } -} diff --git a/crates/flavor-plugin-typescript/src/facts/mod.rs b/crates/flavor-plugin-typescript/src/facts/mod.rs index 1ccffcc..9485f7a 100644 --- a/crates/flavor-plugin-typescript/src/facts/mod.rs +++ b/crates/flavor-plugin-typescript/src/facts/mod.rs @@ -1,23 +1,13 @@ -mod failure; - use flavor_core::{LineIndex, SourceText, Span, Token}; use flavor_grammar::{GrammarNode, GrammarToken, GrammarTree, TokenTextRun}; -use failure::{ - callee_name, constructor_name, is_builtin_error_constructor, matches_configured_callee, - raw_error_argument_constructor, structured_failure_kind, throw_new_expression, - RAW_FAILURE_LITERAL_TOKENS, -}; - use crate::{ ast::TsSourceFile, internal::grammar::{self as kind, Kind}, model::{ - TsDispatchBranchFact, TsFacts, TsFailureMechanism, TsImportFact, TsImportSpecifier, - TsNameFact, TsNameKind, TsRawFailureFact, TsRawFailureKind, TsStructuredFailureFact, + TsDispatchBranchFact, TsFacts, TsImportFact, TsImportSpecifier, TsNameFact, TsNameKind, TsxElementFact, }, - state::{TsFailureSurfaceConfig, TsPluginConfig}, }; type TsNode = GrammarNode; @@ -49,12 +39,11 @@ const SPECIFIER_TOKENS: &[&str] = &[ "KEYWORD_INFER", "KEYWORD_UNIQUE", ]; -pub(crate) fn collect(source_file: &TsSourceFile, config: &TsPluginConfig) -> TsFacts { +pub(crate) fn collect(source_file: &TsSourceFile) -> TsFacts { let tree = GrammarTree::new(source_file.syntax().clone(), kind::schema()); let mut collector = Collector { source: source_file.source(), line_index: source_file.source().line_index(), - failure_surface: &config.failure_surface, facts: legacy_counts(source_file.tokens()), }; collector.collect_node(tree.root()); @@ -76,7 +65,6 @@ fn legacy_counts(tokens: &[Token]) -> TsFacts { struct Collector<'a> { source: &'a SourceText, line_index: LineIndex, - failure_surface: &'a TsFailureSurfaceConfig, facts: TsFacts, } @@ -97,8 +85,6 @@ impl Collector<'_> { } Some("switch_case") => self.collect_branch(&node), Some("import_statement" | "import_declaration") => self.collect_import(&node), - Some("throw_statement") => self.collect_throw_failure(&node), - Some("call_expression") => self.collect_call_failure(&node), Some("jsx_opening_element" | "jsx_self_closing_element") => { self.collect_jsx_element(&node); } @@ -147,91 +133,6 @@ impl Collector<'_> { self.facts.imports.push(import); } - fn collect_throw_failure(&mut self, node: &TsNode) { - let Some(expression) = node.child("expression") else { - return; - }; - let span = node.trimmed_span(self.source); - if let Some(new_expression) = throw_new_expression(&expression) { - let Some(constructor) = constructor_name(&new_expression) else { - return; - }; - if let Some(kind) = structured_failure_kind(&constructor, self.failure_surface) { - self.facts - .structured_failures - .push(TsStructuredFailureFact { - kind, - mechanism: TsFailureMechanism::ThrowNew, - callee: constructor, - span, - line: self.line_for(span), - }); - return; - } - if is_builtin_error_constructor(&constructor) { - self.facts.raw_failures.push(TsRawFailureFact { - kind: TsRawFailureKind::BuiltinError, - mechanism: TsFailureMechanism::Throw, - constructor: Some(constructor), - callee: None, - span, - line: self.line_for(span), - }); - } - return; - } - - if expression - .child_tokens_any(RAW_FAILURE_LITERAL_TOKENS) - .next() - .is_some() - { - self.facts.raw_failures.push(TsRawFailureFact { - kind: TsRawFailureKind::Literal, - mechanism: TsFailureMechanism::Throw, - constructor: None, - callee: None, - span, - line: self.line_for(span), - }); - } - } - - fn collect_call_failure(&mut self, node: &TsNode) { - let Some(callee) = callee_name(node) else { - return; - }; - if let Some(kind) = structured_failure_kind(&callee, self.failure_surface) { - let span = node.trimmed_span(self.source); - self.facts - .structured_failures - .push(TsStructuredFailureFact { - kind, - mechanism: TsFailureMechanism::Call, - callee, - span, - line: self.line_for(span), - }); - return; - } - - if !matches_configured_callee(&callee, &self.failure_surface.raw_reject_callees) { - return; - } - let Some(constructor) = raw_error_argument_constructor(node) else { - return; - }; - let span = node.trimmed_span(self.source); - self.facts.raw_failures.push(TsRawFailureFact { - kind: TsRawFailureKind::BuiltinError, - mechanism: TsFailureMechanism::Call, - constructor: Some(constructor), - callee: Some(callee), - span, - line: self.line_for(span), - }); - } - fn import_fact(&self, node: &TsNode) -> Option { let source = node .token("STRING_LITERAL") diff --git a/crates/flavor-plugin-typescript/src/lib.rs b/crates/flavor-plugin-typescript/src/lib.rs index 567416e..b906795 100644 --- a/crates/flavor-plugin-typescript/src/lib.rs +++ b/crates/flavor-plugin-typescript/src/lib.rs @@ -11,11 +11,10 @@ mod visit; use flavor_core::SourceText; pub use model::{ - TsAnalysisOutput, TsDispatchBranchFact, TsFacts, TsFailureMechanism, TsImportFact, - TsImportSpecifier, TsNameFact, TsNameKind, TsRawFailureFact, TsRawFailureKind, - TsStructuredFailureFact, TsStructuredFailureKind, TsTokenKind, TsxElementFact, + TsAnalysisOutput, TsDispatchBranchFact, TsFacts, TsImportFact, TsImportSpecifier, TsNameFact, + TsNameKind, TsTokenKind, TsxElementFact, }; -pub use state::{SourceMode, TsFailureSurfaceConfig, TsPluginConfig, TsPluginState}; +pub use state::{SourceMode, TsPluginConfig, TsPluginState}; #[derive(Debug, Clone)] pub struct TsPluginAnalyzer { @@ -32,7 +31,7 @@ impl TsPluginAnalyzer { pub fn run(&mut self, source: SourceText) -> TsAnalysisOutput { let tokens = lexer::scan(&source, self.state.config()); let parse_output = parser::parse(source, tokens, self.state.config()); - let facts = facts::collect(&parse_output.source_file, self.state.config()); + let facts = facts::collect(&parse_output.source_file); let (source, tokens, syntax) = parse_output.source_file.into_parts(); TsAnalysisOutput { source, diff --git a/crates/flavor-plugin-typescript/src/model.rs b/crates/flavor-plugin-typescript/src/model.rs index 89e0472..3f811d3 100644 --- a/crates/flavor-plugin-typescript/src/model.rs +++ b/crates/flavor-plugin-typescript/src/model.rs @@ -18,8 +18,6 @@ pub struct TsFacts { pub names: Vec, pub dispatch_branches: Vec, pub imports: Vec, - pub raw_failures: Vec, - pub structured_failures: Vec, pub jsx_elements: Vec, } @@ -73,72 +71,6 @@ pub enum TsImportSpecifier { Namespace(String), } -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum TsFailureMechanism { - Throw, - Call, - ThrowNew, -} - -impl TsFailureMechanism { - pub fn label(self) -> &'static str { - match self { - TsFailureMechanism::Throw => "throw", - TsFailureMechanism::Call => "call", - TsFailureMechanism::ThrowNew => "throw-new", - } - } -} - -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum TsRawFailureKind { - BuiltinError, - Literal, -} - -impl TsRawFailureKind { - pub fn label(self) -> &'static str { - match self { - TsRawFailureKind::BuiltinError => "builtin-error", - TsRawFailureKind::Literal => "literal", - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct TsRawFailureFact { - pub kind: TsRawFailureKind, - pub mechanism: TsFailureMechanism, - pub constructor: Option, - pub callee: Option, - pub span: Span, - pub line: usize, -} - -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub enum TsStructuredFailureKind { - Guard, - Factory, -} - -impl TsStructuredFailureKind { - pub fn label(self) -> &'static str { - match self { - TsStructuredFailureKind::Guard => "guard", - TsStructuredFailureKind::Factory => "factory", - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct TsStructuredFailureFact { - pub kind: TsStructuredFailureKind, - pub mechanism: TsFailureMechanism, - pub callee: String, - pub span: Span, - pub line: usize, -} - #[derive(Debug, Clone, Eq, PartialEq)] pub struct TsxElementFact { pub name: String, diff --git a/crates/flavor-plugin-typescript/src/plugin.rs b/crates/flavor-plugin-typescript/src/plugin.rs index 94a37b5..c76bf65 100644 --- a/crates/flavor-plugin-typescript/src/plugin.rs +++ b/crates/flavor-plugin-typescript/src/plugin.rs @@ -4,8 +4,7 @@ use flavor_core::{diagnostics, product, FactPayload, GrammarProduct, PendingFact use flavor_shared::product as shared_product; use crate::{ - internal::grammar, run as run_ts, SourceMode, TsFailureSurfaceConfig, TsImportSpecifier, - TsNameKind, TsPluginConfig, + internal::grammar, run as run_ts, SourceMode, TsImportSpecifier, TsNameKind, TsPluginConfig, }; pub fn prewarm() { @@ -58,7 +57,7 @@ pub fn satisfy_script( source, line_offset, tsx, - ts_config(tsx, TsFailureSurfaceConfig::default()), + ts_config(tsx), products, ); } @@ -119,29 +118,6 @@ pub fn satisfy_script_with_config( .span(fact.span) .line(fact.line + line_offset) })); - facts.extend(output.facts.raw_failures.iter().map(|fact| { - PendingFact::new( - "error.raw_failure", - FactPayload::new() - .text("kind", fact.kind.label()) - .text("mechanism", fact.mechanism.label()) - .text("constructor", fact.constructor.clone().unwrap_or_default()) - .text("callee", fact.callee.clone().unwrap_or_default()), - ) - .span(fact.span) - .line(fact.line + line_offset) - })); - facts.extend(output.facts.structured_failures.iter().map(|fact| { - PendingFact::new( - "error.structured_failure", - FactPayload::new() - .text("kind", fact.kind.label()) - .text("mechanism", fact.mechanism.label()) - .text("callee", fact.callee.clone()), - ) - .span(fact.span) - .line(fact.line + line_offset) - })); product(products, "typescript", entrypoint, diagnostics, facts); } @@ -175,14 +151,13 @@ pub fn satisfy_script_with_config( product(products, "tsx", entrypoint, Vec::new(), facts); } -fn ts_config(tsx: bool, failure_surface: TsFailureSurfaceConfig) -> TsPluginConfig { +fn ts_config(tsx: bool) -> TsPluginConfig { TsPluginConfig { source_mode: if tsx { SourceMode::Tsx } else { SourceMode::TypeScript }, - failure_surface, ..Default::default() } } diff --git a/crates/flavor-plugin-typescript/src/state/config.rs b/crates/flavor-plugin-typescript/src/state/config.rs index 3b1a580..de9bb30 100644 --- a/crates/flavor-plugin-typescript/src/state/config.rs +++ b/crates/flavor-plugin-typescript/src/state/config.rs @@ -3,7 +3,6 @@ pub struct TsPluginConfig { pub source_mode: SourceMode, pub jsx: JsxConfig, pub decorators: DecoratorsConfig, - pub failure_surface: TsFailureSurfaceConfig, } impl Default for TsPluginConfig { @@ -12,27 +11,6 @@ impl Default for TsPluginConfig { source_mode: SourceMode::TypeScript, jsx: JsxConfig::default(), decorators: DecoratorsConfig::default(), - failure_surface: TsFailureSurfaceConfig::default(), - } - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct TsFailureSurfaceConfig { - pub structured_guards: Vec, - pub structured_factories: Vec, - pub raw_reject_callees: Vec, -} - -impl Default for TsFailureSurfaceConfig { - fn default() -> Self { - Self { - structured_guards: Vec::new(), - structured_factories: Vec::new(), - raw_reject_callees: ["Promise.reject", "reject"] - .into_iter() - .map(str::to_string) - .collect(), } } } diff --git a/crates/flavor-plugin-typescript/src/state/mod.rs b/crates/flavor-plugin-typescript/src/state/mod.rs index 2d00b82..ab6d67d 100644 --- a/crates/flavor-plugin-typescript/src/state/mod.rs +++ b/crates/flavor-plugin-typescript/src/state/mod.rs @@ -2,6 +2,6 @@ pub mod config; use flavor_shared::PluginState; -pub use config::{DecoratorsConfig, JsxConfig, SourceMode, TsFailureSurfaceConfig, TsPluginConfig}; +pub use config::{DecoratorsConfig, JsxConfig, SourceMode, TsPluginConfig}; pub type TsPluginState = PluginState; diff --git a/crates/flavor-plugin-typescript/tests/contract.rs b/crates/flavor-plugin-typescript/tests/contract.rs index 2914ebf..54e01d6 100644 --- a/crates/flavor-plugin-typescript/tests/contract.rs +++ b/crates/flavor-plugin-typescript/tests/contract.rs @@ -3,10 +3,7 @@ use flavor_grammar::{ parse_contract, parse_contract_values, GrammarContractExpectation, GrammarEntryValueExpectation, GrammarSectionExpectation, }; -use flavor_plugin_typescript::{ - run, SourceMode, TsFailureMechanism, TsFailureSurfaceConfig, TsImportSpecifier, TsNameKind, - TsPluginConfig, TsRawFailureKind, TsStructuredFailureKind, -}; +use flavor_plugin_typescript::{run, SourceMode, TsImportSpecifier, TsNameKind, TsPluginConfig}; const TYPESCRIPT_METADATA: &str = include_str!("../../../grammars/typescript/metadata.json"); const TSX_METADATA: &str = include_str!("../../../grammars/typescript/metadata.json"); @@ -21,8 +18,6 @@ const TYPESCRIPT_CONTRACT: GrammarContractExpectation<'static> = GrammarContract name: "facts", entries: &[ "dispatch.branch", - "error.raw_failure", - "error.structured_failure", "module.counts", "module.import", "name.binding", @@ -117,31 +112,6 @@ const TYPESCRIPT_VALUES: &[GrammarEntryValueExpectation<'static>] = &[ key: "dispatch.branch", contains: &["TsDispatchBranchFact", "payload.lines", "span", "line"], }, - GrammarEntryValueExpectation { - section: "facts", - key: "error.raw_failure", - contains: &[ - "TsRawFailureFact", - "payload.kind", - "payload.mechanism", - "payload.constructor", - "payload.callee", - "span", - "line", - ], - }, - GrammarEntryValueExpectation { - section: "facts", - key: "error.structured_failure", - contains: &[ - "TsStructuredFailureFact", - "payload.kind", - "payload.mechanism", - "payload.callee", - "span", - "line", - ], - }, ]; const TSX_CONTRACT: GrammarContractExpectation<'static> = GrammarContractExpectation { name: "tsx", @@ -299,82 +269,6 @@ fn typescript_contract_diagnostics() { assert!(span.end as usize <= "class Broken".len()); } -#[test] -fn typescript_failure_surface_facts() { - parse_contract(TYPESCRIPT_METADATA, &TYPESCRIPT_CONTRACT).unwrap(); - - let output = run( - SourceText::new( - "failure.ts", - "function load(input: string) {\n\ - ensure(input);\n\ - throw new Error('missing');\n\ - }\n\ - function later(input: string) {\n\ - return Promise.reject(new TypeError(input));\n\ - }\n\ - function callback(reject: (error: Error) => void) {\n\ - reject(new RangeError('bad'));\n\ - }\n\ - function custom(input: string) {\n\ - return DomainError.missing(input);\n\ - }\n\ - function stable(input: string) {\n\ - throw new DomainError(input);\n\ - }\n", - ), - TsPluginConfig { - failure_surface: TsFailureSurfaceConfig { - structured_guards: vec!["ensure".to_string()], - structured_factories: vec!["DomainError".to_string()], - ..Default::default() - }, - ..Default::default() - }, - ); - - assert!(output.diagnostics.is_empty(), "{:?}", output.diagnostics); - assert_eq!(output.facts.raw_failures.len(), 3); - let raw = &output.facts.raw_failures[0]; - assert_eq!(raw.kind, TsRawFailureKind::BuiltinError); - assert_eq!(raw.mechanism, TsFailureMechanism::Throw); - assert_eq!(raw.constructor.as_deref(), Some("Error")); - assert_eq!(raw.callee, None); - assert_eq!(raw.line, 3); - assert!(output.source.slice(raw.span).starts_with("throw new Error")); - assert!(output.facts.raw_failures.iter().any(|fact| { - fact.mechanism == TsFailureMechanism::Call - && fact.constructor.as_deref() == Some("TypeError") - && fact.callee.as_deref() == Some("Promise.reject") - && fact.line == 6 - })); - assert!(output.facts.raw_failures.iter().any(|fact| { - fact.mechanism == TsFailureMechanism::Call - && fact.constructor.as_deref() == Some("RangeError") - && fact.callee.as_deref() == Some("reject") - && fact.line == 9 - })); - - assert!(output.facts.structured_failures.iter().any(|fact| { - fact.kind == TsStructuredFailureKind::Guard - && fact.mechanism == TsFailureMechanism::Call - && fact.callee == "ensure" - && fact.line == 2 - })); - assert!(output.facts.structured_failures.iter().any(|fact| { - fact.kind == TsStructuredFailureKind::Factory - && fact.mechanism == TsFailureMechanism::Call - && fact.callee == "DomainError.missing" - && fact.line == 12 - })); - assert!(output.facts.structured_failures.iter().any(|fact| { - fact.kind == TsStructuredFailureKind::Factory - && fact.mechanism == TsFailureMechanism::ThrowNew - && fact.callee == "DomainError" - && fact.line == 15 - })); -} - #[test] fn tsx_contract_sections() { parse_contract_values(TSX_METADATA, &TSX_CONTRACT, TSX_VALUES).unwrap(); diff --git a/crates/flavor-plugin-vue/src/plugin.rs b/crates/flavor-plugin-vue/src/plugin.rs index ecd3ac8..bd7ac60 100644 --- a/crates/flavor-plugin-vue/src/plugin.rs +++ b/crates/flavor-plugin-vue/src/plugin.rs @@ -1,9 +1,7 @@ use std::collections::BTreeMap; use flavor_core::{diagnostics, product, GrammarProduct, PendingFact, SourceText}; -use flavor_plugin_typescript::{ - plugin as typescript_plugin, SourceMode, TsFailureSurfaceConfig, TsPluginConfig, -}; +use flavor_plugin_typescript::{plugin as typescript_plugin, SourceMode, TsPluginConfig}; use flavor_shared::product as shared_product; use crate::{run as run_vue, sfc::VueSfcBlock, template, VuePluginConfig}; @@ -16,24 +14,6 @@ pub fn prewarm() { pub fn satisfy(entrypoint: &F, path: &str, source: &str, products: &mut Vec) where F: Fn(&str) -> Option<&'static str>, -{ - satisfy_with_failure_surface( - entrypoint, - path, - source, - TsFailureSurfaceConfig::default(), - products, - ); -} - -pub fn satisfy_with_failure_surface( - entrypoint: &F, - path: &str, - source: &str, - failure_surface: TsFailureSurfaceConfig, - products: &mut Vec, -) where - F: Fn(&str) -> Option<&'static str>, { let Some(vue_entrypoint) = entrypoint("vue-sfc") else { return; @@ -46,25 +26,11 @@ pub fn satisfy_with_failure_surface( let mut facts = Vec::new(); if let Some(block) = output.descriptor.script { push_block_fact("descriptor.script", &block, &mut facts); - push_embedded_script( - entrypoint, - path, - block, - &failure_surface, - &mut facts, - products, - ); + push_embedded_script(entrypoint, path, block, &mut facts, products); } if let Some(block) = output.descriptor.script_setup { push_block_fact("descriptor.script_setup", &block, &mut facts); - push_embedded_script( - entrypoint, - path, - block, - &failure_surface, - &mut facts, - products, - ); + push_embedded_script(entrypoint, path, block, &mut facts, products); } product(products, "vue-sfc", vue_entrypoint, diagnostics, facts); } @@ -82,7 +48,6 @@ fn push_embedded_script( entrypoint: &F, path: &str, block: VueSfcBlock, - failure_surface: &TsFailureSurfaceConfig, facts: &mut Vec, products: &mut Vec, ) where @@ -108,7 +73,6 @@ fn push_embedded_script( } else { SourceMode::TypeScript }, - failure_surface: failure_surface.clone(), ..Default::default() }; typescript_plugin::satisfy_script_with_config( diff --git a/grammars/typescript/metadata.json b/grammars/typescript/metadata.json index fb6bc7e..bb8618d 100644 --- a/grammars/typescript/metadata.json +++ b/grammars/typescript/metadata.json @@ -62,8 +62,6 @@ }, "facts": { "dispatch.branch": "switch_case full range -> TsDispatchBranchFact(span, line, lines) -> Fact(payload.lines, span, line)", - "error.raw_failure": "throw_statement with built-in Error constructor or literal, or configured reject call with direct built-in Error construction -> TsRawFailureFact(kind, mechanism, constructor, callee, span, line) -> Fact(payload.kind, payload.mechanism, payload.constructor, payload.callee, span, line)", - "error.structured_failure": "configured guard/factory call or throw-new -> TsStructuredFailureFact(kind, mechanism, callee, span, line) -> Fact(payload.kind, payload.mechanism, payload.callee, span, line)", "module.counts": "import and export tokens -> TsFacts counts", "module.import": "import_statement.source and import_clause -> TsImportFact(source, specifiers, span, line) -> Fact(payload.source, payload.type_only, payload.default_imports, payload.named_imports, payload.namespace_imports, span, line)", "name.binding": "variable_declarator.name identifiers -> TsNameFact(kind:binding, span, line) -> Fact(payload.name, payload.kind, payload.issue_kind, span, line)",