From 8ba8133aa7839811e9630a583c1f59f8e0f521fc Mon Sep 17 00:00:00 2001 From: Bart de Water <496367+bdewater@users.noreply.github.com> Date: Sat, 4 Nov 2023 09:56:46 -0400 Subject: [PATCH 1/4] Annotate Active Support string extensions --- rbi/annotations/activesupport.rbi | 141 +++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 3 deletions(-) diff --git a/rbi/annotations/activesupport.rbi b/rbi/annotations/activesupport.rbi index eebf7893..956a854c 100644 --- a/rbi/annotations/activesupport.rbi +++ b/rbi/annotations/activesupport.rbi @@ -61,6 +61,9 @@ class Object sig { returns(T::Boolean) } def blank?; end + sig { returns(FalseClass) } + def html_safe?; end + sig { returns(T.nilable(T.self_type)) } def presence; end @@ -238,6 +241,9 @@ class Numeric sig { returns(FalseClass) } def blank?; end + sig { returns(TrueClass) } + def html_safe?; end + # @shim: since `present?` is always true, `presence` always returns `self` sig { returns(T.self_type) } def presence; end @@ -271,11 +277,140 @@ class Symbol end class String - # alias for `#start_with?` - sig { params(string_or_regexp: T.any(String, Regexp)).returns(T::Boolean) } - def starts_with?(*string_or_regexp); end + sig { returns(TrueClass) } + def acts_like_string?; end + + sig { params(position: T.any(Integer, Regexp, String, T::Range[Integer])).returns(T.nilable(String)) } + def at(position); end + + sig { params(first_letter: Symbol).returns(String) } + def camelcase(first_letter = :upper); end + + sig { params(first_letter: Symbol).returns(String) } + def camelize(first_letter = :upper); end + + sig { returns(String) } + def classify; end + + sig { returns(Module) } + def constantize; end + + sig { returns(String) } + def dasherize; end + + sig { returns(String) } + def deconstantize; end + + sig { returns(String) } + def demodulize; end # alias for `#end_with?` sig { params(string_or_regexp: T.any(String, Regexp)).returns(T::Boolean) } def ends_with?(*string_or_regexp); end + + sig { params(string: String).returns(T::Boolean) } + def exclude?(string); end + + sig { params(limit: Integer).returns(String) } + def first(limit = 1); end + + sig { params(separate_class_name_and_id_with_underscore: T.untyped).returns(String) } + def foreign_key(separate_class_name_and_id_with_underscore = true); end + + sig { params(position: Integer).returns(String) } + def from(position); end + + sig { returns(ActiveSupport::SafeBuffer) } + def html_safe; end + + sig { params(capitalize: T.untyped, keep_id_suffix: T.untyped).returns(String) } + def humanize(capitalize: true, keep_id_suffix: false); end + + sig { params(zone: T.nilable(T.any(ActiveSupport::TimeZone, String))).returns(T.any(ActiveSupport::TimeWithZone, Time)) } + def in_time_zone(zone = ::Time.zone); end + + sig { params(amount: Integer, indent_string: T.nilable(String), indent_empty_lines: T.untyped).returns(String) } + def indent(amount, indent_string = nil, indent_empty_lines = false); end + + sig { params(amount: Integer, indent_string: T.nilable(String), indent_empty_lines: T.untyped).returns(T.nilable(String)) } + def indent!(amount, indent_string = nil, indent_empty_lines = false); end + + sig { returns(ActiveSupport::StringInquirer) } + def inquiry; end + + sig { returns(T::Boolean) } + def is_utf8?; end + + sig { params(limit: Integer).returns(String) } + def last(limit = 1); end + + sig { returns(ActiveSupport::Multibyte::Chars) } + def mb_chars; end + + sig { params(separator: T.nilable(String), preserve_case: T.untyped, locale: T.nilable(Symbol)).returns(String) } + def parameterize(separator: "-", preserve_case: false, locale: nil); end + + sig { params(count: T.nilable(T.any(Integer, Symbol)), locale: T.nilable(Symbol)).returns(String) } + def pluralize(count = nil, locale = :en); end + + sig { params(patterns: T.nilable(T.any(Regexp, String))).returns(String) } + def remove(*patterns); end + + sig { params(patterns: T.nilable(T.any(Regexp, String))).returns(String) } + def remove!(*patterns); end + + sig { returns(T.nilable(Module)) } + def safe_constantize; end + + sig { params(locale: T.nilable(Symbol)).returns(String) } + def singularize(locale = :en); end + + sig { returns(String) } + def squish; end + + sig { returns(String) } + def squish!; end + + # alias for `#start_with?` + sig { params(string_or_regexp: T.any(String, Regexp)).returns(T::Boolean) } + def starts_with?(*string_or_regexp); end + + sig { returns(String) } + def strip_heredoc; end + + sig { returns(String) } + def tableize; end + + sig { params(keep_id_suffix: T.untyped).returns(String) } + def titlecase(keep_id_suffix: false); end + + sig { params(keep_id_suffix: T.untyped).returns(String) } + def titleize(keep_id_suffix: false); end + + sig { params(position: Integer).returns(String) } + def to(position); end + + sig { returns(T.nilable(Date)) } + def to_date; end + + sig { returns(T.nilable(DateTime)) } + def to_datetime; end + + sig { params(form: T.nilable(Symbol)).returns(T.nilable(Time)) } + def to_time(form = :local); end + + sig { params(truncate_to: Integer, options: T::Hash[Symbol, T.untyped]).returns(String) } + def truncate(truncate_to, options = {}); end + + sig { params(truncate_to: Integer, omission: T.nilable(String)).returns(String) } + def truncate_bytes(truncate_to, omission: "…"); end + + sig { params(words_count: Integer, options: T::Hash[Symbol, T.untyped]).returns(String) } + def truncate_words(words_count, options = {}); end + + sig { returns(String) } + def underscore; end + + sig { returns(String) } + def upcase_first; end end From 565db4af642be0c268d9c0a16e1ced2445398ef5 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 9 Jan 2024 12:02:55 -0500 Subject: [PATCH 2/4] Fix/Improve ActiveSupport String sigs --- rbi/annotations/activesupport.rbi | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/rbi/annotations/activesupport.rbi b/rbi/annotations/activesupport.rbi index 956a854c..3945cbe2 100644 --- a/rbi/annotations/activesupport.rbi +++ b/rbi/annotations/activesupport.rbi @@ -280,7 +280,11 @@ class String sig { returns(TrueClass) } def acts_like_string?; end - sig { params(position: T.any(Integer, Regexp, String, T::Range[Integer])).returns(T.nilable(String)) } + # This is the subset of `#[]` sigs that have just 1 parameter. + # https://github.com/sorbet/sorbet/blob/40ad87b4dc7be23fa00c1369ac9f927053c68907/rbi/core/string.rbi#L270-L303 + sig { params(position: Integer).returns(T.nilable(String)) } + sig { params(position: T.any(T::Range[Integer], Regexp)).returns(T.nilable(String)) } + sig { params(position: String).returns(T.nilable(String)) } def at(position); end sig { params(first_letter: Symbol).returns(String) } @@ -292,7 +296,7 @@ class String sig { returns(String) } def classify; end - sig { returns(Module) } + sig { returns(T.untyped) } def constantize; end sig { returns(String) } @@ -347,22 +351,22 @@ class String sig { returns(ActiveSupport::Multibyte::Chars) } def mb_chars; end - sig { params(separator: T.nilable(String), preserve_case: T.untyped, locale: T.nilable(Symbol)).returns(String) } + sig { params(separator: String, preserve_case: T::Boolean, locale: T.nilable(Symbol)).returns(String) } def parameterize(separator: "-", preserve_case: false, locale: nil); end sig { params(count: T.nilable(T.any(Integer, Symbol)), locale: T.nilable(Symbol)).returns(String) } def pluralize(count = nil, locale = :en); end - sig { params(patterns: T.nilable(T.any(Regexp, String))).returns(String) } + sig { params(patterns: T.any(String, Regexp)).returns(String) } def remove(*patterns); end - sig { params(patterns: T.nilable(T.any(Regexp, String))).returns(String) } + sig { params(patterns: T.any(String, Regexp)).returns(String) } def remove!(*patterns); end - sig { returns(T.nilable(Module)) } + sig { returns(T.untyped) } def safe_constantize; end - sig { params(locale: T.nilable(Symbol)).returns(String) } + sig { params(locale: Symbol).returns(String) } def singularize(locale = :en); end sig { returns(String) } @@ -390,22 +394,22 @@ class String sig { params(position: Integer).returns(String) } def to(position); end - sig { returns(T.nilable(Date)) } + sig { returns(::Date) } def to_date; end - sig { returns(T.nilable(DateTime)) } + sig { returns(::DateTime) } def to_datetime; end sig { params(form: T.nilable(Symbol)).returns(T.nilable(Time)) } def to_time(form = :local); end - sig { params(truncate_to: Integer, options: T::Hash[Symbol, T.untyped]).returns(String) } + sig { params(truncate_to: Integer, options: T::Hash[Symbol, T.anything]).returns(String) } def truncate(truncate_to, options = {}); end sig { params(truncate_to: Integer, omission: T.nilable(String)).returns(String) } def truncate_bytes(truncate_to, omission: "…"); end - sig { params(words_count: Integer, options: T::Hash[Symbol, T.untyped]).returns(String) } + sig { params(words_count: Integer, options: T::Hash[Symbol, T.anything]).returns(String) } def truncate_words(words_count, options = {}); end sig { returns(String) } From a0eb6ba264d3014dae57a1af6f54e3788c7c46bc Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 9 Jan 2024 11:54:35 -0500 Subject: [PATCH 3/4] Prefer `T::Boolean` params over `T.untyped` --- rbi/annotations/activesupport.rbi | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rbi/annotations/activesupport.rbi b/rbi/annotations/activesupport.rbi index 3945cbe2..e2651bf6 100644 --- a/rbi/annotations/activesupport.rbi +++ b/rbi/annotations/activesupport.rbi @@ -318,7 +318,7 @@ class String sig { params(limit: Integer).returns(String) } def first(limit = 1); end - sig { params(separate_class_name_and_id_with_underscore: T.untyped).returns(String) } + sig { params(separate_class_name_and_id_with_underscore: T::Boolean).returns(String) } def foreign_key(separate_class_name_and_id_with_underscore = true); end sig { params(position: Integer).returns(String) } @@ -327,16 +327,16 @@ class String sig { returns(ActiveSupport::SafeBuffer) } def html_safe; end - sig { params(capitalize: T.untyped, keep_id_suffix: T.untyped).returns(String) } + sig { params(capitalize: T::Boolean, keep_id_suffix: T::Boolean).returns(String) } def humanize(capitalize: true, keep_id_suffix: false); end sig { params(zone: T.nilable(T.any(ActiveSupport::TimeZone, String))).returns(T.any(ActiveSupport::TimeWithZone, Time)) } def in_time_zone(zone = ::Time.zone); end - sig { params(amount: Integer, indent_string: T.nilable(String), indent_empty_lines: T.untyped).returns(String) } + sig { params(amount: Integer, indent_string: T.nilable(String), indent_empty_lines: T::Boolean).returns(String) } def indent(amount, indent_string = nil, indent_empty_lines = false); end - sig { params(amount: Integer, indent_string: T.nilable(String), indent_empty_lines: T.untyped).returns(T.nilable(String)) } + sig { params(amount: Integer, indent_string: T.nilable(String), indent_empty_lines: T::Boolean).returns(T.nilable(String)) } def indent!(amount, indent_string = nil, indent_empty_lines = false); end sig { returns(ActiveSupport::StringInquirer) } @@ -385,10 +385,10 @@ class String sig { returns(String) } def tableize; end - sig { params(keep_id_suffix: T.untyped).returns(String) } + sig { params(keep_id_suffix: T::Boolean).returns(String) } def titlecase(keep_id_suffix: false); end - sig { params(keep_id_suffix: T.untyped).returns(String) } + sig { params(keep_id_suffix: T::Boolean).returns(String) } def titleize(keep_id_suffix: false); end sig { params(position: Integer).returns(String) } From 8b67e001eb74938445f22eb2b2dbd09e4d9cfd8a Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 9 Jan 2024 11:54:48 -0500 Subject: [PATCH 4/4] Annotate remaining String APIs --- rbi/annotations/activesupport.rbi | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/rbi/annotations/activesupport.rbi b/rbi/annotations/activesupport.rbi index e2651bf6..d64f3abc 100644 --- a/rbi/annotations/activesupport.rbi +++ b/rbi/annotations/activesupport.rbi @@ -287,6 +287,12 @@ class String sig { params(position: String).returns(T.nilable(String)) } def at(position); end + sig { returns(String) } + def as_json; end + + sig { returns(T::Boolean) } + def blank?; end + sig { params(first_letter: Symbol).returns(String) } def camelcase(first_letter = :upper); end @@ -312,6 +318,9 @@ class String sig { params(string_or_regexp: T.any(String, Regexp)).returns(T::Boolean) } def ends_with?(*string_or_regexp); end + sig { returns(String) } + def downcase_first; end + sig { params(string: String).returns(T::Boolean) } def exclude?(string); end