@@ -44,16 +44,26 @@ fn extract_rules_from_sql(content: &str) -> Result<BTreeMap<String, RuleInfo>> {
4444 // Look for pattern: 'rule_name' as "name!",
4545 if line. contains ( " as \" name!\" " ) {
4646 if let Some ( name) = extract_string_literal ( line) {
47- // Look ahead for categories
47+ // Look ahead for categories and remediation URL
4848 let mut categories = None ;
49+ let mut remediation_url = None ;
4950
5051 for next_line in lines[ i..] . iter ( ) . take ( 30 ) {
5152 let next_line = next_line. trim ( ) ;
5253
5354 // Extract categories from pattern: array['CATEGORY'] as "categories!",
5455 if next_line. contains ( " as \" categories!\" " ) {
5556 categories = extract_categories ( next_line) ;
56- break ; // Stop once we have categories
57+ }
58+
59+ // Extract remediation URL from pattern: 'url' as "remediation!",
60+ if next_line. contains ( " as \" remediation!\" " ) {
61+ remediation_url = extract_string_literal ( next_line) ;
62+ }
63+
64+ // Stop once we have both
65+ if categories. is_some ( ) && remediation_url. is_some ( ) {
66+ break ;
5767 }
5868 }
5969
@@ -66,6 +76,7 @@ fn extract_rules_from_sql(content: &str) -> Result<BTreeMap<String, RuleInfo>> {
6676 snake_case : name. clone ( ) ,
6777 camel_case : snake_to_camel_case ( & name) ,
6878 categories : cats,
79+ url : remediation_url,
6980 } ,
7081 ) ;
7182 }
@@ -79,6 +90,7 @@ fn extract_rules_from_sql(content: &str) -> Result<BTreeMap<String, RuleInfo>> {
7990 snake_case : "unknown" . to_string ( ) ,
8091 camel_case : "unknown" . to_string ( ) ,
8192 categories : vec ! [ "UNKNOWN" . to_string( ) ] ,
93+ url : None ,
8294 } ,
8395 ) ;
8496
@@ -137,42 +149,17 @@ fn snake_to_camel_case(s: &str) -> String {
137149 Case :: Camel . convert ( s)
138150}
139151
152+ /// Check if a string is a valid URL (simple check for http/https)
153+ fn is_valid_url ( s : & str ) -> bool {
154+ s. starts_with ( "http://" ) || s. starts_with ( "https://" )
155+ }
156+
140157struct RuleInfo {
141158 #[ allow( dead_code) ]
142159 snake_case : String ,
143160 camel_case : String ,
144161 categories : Vec < String > ,
145- }
146-
147- /// Build remediation URL from rule name
148- /// Must match the logic in crates/pgls_splinter/src/convert.rs
149- fn build_remediation_url ( name : & str ) -> String {
150- let lint_id = match name {
151- "unindexed_foreign_keys" => "0001_unindexed_foreign_keys" ,
152- "auth_users_exposed" => "0002_auth_users_exposed" ,
153- "auth_rls_initplan" => "0003_auth_rls_initplan" ,
154- "no_primary_key" => "0004_no_primary_key" ,
155- "unused_index" => "0005_unused_index" ,
156- "multiple_permissive_policies" => "0006_multiple_permissive_policies" ,
157- "policy_exists_rls_disabled" => "0007_policy_exists_rls_disabled" ,
158- "rls_enabled_no_policy" => "0008_rls_enabled_no_policy" ,
159- "duplicate_index" => "0009_duplicate_index" ,
160- "security_definer_view" => "0010_security_definer_view" ,
161- "function_search_path_mutable" => "0011_function_search_path_mutable" ,
162- "rls_disabled_in_public" => "0013_rls_disabled_in_public" ,
163- "extension_in_public" => "0014_extension_in_public" ,
164- "rls_references_user_metadata" => "0015_rls_references_user_metadata" ,
165- "materialized_view_in_api" => "0016_materialized_view_in_api" ,
166- "foreign_table_in_api" => "0017_foreign_table_in_api" ,
167- "unsupported_reg_types" => "unsupported_reg_types" ,
168- "insecure_queue_exposed_in_api" => "0019_insecure_queue_exposed_in_api" ,
169- "table_bloat" => "0020_table_bloat" ,
170- "fkey_to_auth_unique" => "0021_fkey_to_auth_unique" ,
171- "extension_versions_outdated" => "0022_extension_versions_outdated" ,
172- _ => return "https://supabase.com/docs/guides/database/database-linter" . to_string ( ) ,
173- } ;
174-
175- format ! ( "https://supabase.com/docs/guides/database/database-linter?lint={lint_id}" )
162+ url : Option < String > ,
176163}
177164
178165/// Update the categories.rs file with splinter rules
@@ -190,7 +177,15 @@ fn update_categories_file(rules: BTreeMap<String, RuleInfo>) -> Result<()> {
190177 // In practice, splinter rules have only one category
191178 rule. categories . iter ( ) . map ( |category| {
192179 let group = category. to_lowercase ( ) ;
193- let url = build_remediation_url ( & rule. snake_case ) ;
180+
181+ // Use extracted URL if it's a valid URL, otherwise fallback to default
182+ let url = rule
183+ . url
184+ . as_ref ( )
185+ . filter ( |u| is_valid_url ( u) )
186+ . map ( |u| u. as_str ( ) )
187+ . unwrap_or ( "https://supabase.com/docs/guides/database/database-linter" ) ;
188+
194189 (
195190 group. clone ( ) ,
196191 format ! (
0 commit comments