Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Annotate Active Support string extensions #185

Merged
merged 4 commits into from
Jan 9, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 151 additions & 3 deletions rbi/annotations/activesupport.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -271,11 +277,153 @@ 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

# 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 { 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

sig { params(first_letter: Symbol).returns(String) }
def camelize(first_letter = :upper); end

sig { returns(String) }
def classify; end

sig { returns(T.untyped) }
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 { returns(String) }
def downcase_first; 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::Boolean).returns(String) }
def foreign_key(separate_class_name_and_id_with_underscore = true); end

This comment was marked as resolved.


sig { params(position: Integer).returns(String) }
def from(position); end

sig { returns(ActiveSupport::SafeBuffer) }
def html_safe; end

sig { params(capitalize: T::Boolean, keep_id_suffix: T::Boolean).returns(String) }
def humanize(capitalize: true, keep_id_suffix: false); end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always a tough balance to strike when adding types to untyped code :)

I opted to err on the side of flexibility here since any truthy value will work here but happy to change to boolean if you want a more strict type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with its usages so if you think being flexible is good for cases where an object is supplied I'm okay with it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My $0.02: We should enforce booleans, as it improves the clarity of the API.

It's easy enough for callers to toss in a !!x if they need.


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::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::Boolean).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: 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
Comment on lines +366 to +367
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woah this is a weird sig (the implementation itself is strange, because the count can be the locale if you only pass 1 arg).


sig { params(patterns: T.any(String, Regexp)).returns(String) }
def remove(*patterns); end

sig { params(patterns: T.any(String, Regexp)).returns(String) }
def remove!(*patterns); end

sig { returns(T.untyped) }
def safe_constantize; end

sig { params(locale: 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::Boolean).returns(String) }
def titlecase(keep_id_suffix: false); end

sig { params(keep_id_suffix: T::Boolean).returns(String) }
def titleize(keep_id_suffix: false); end

sig { params(position: Integer).returns(String) }
def to(position); end

sig { returns(::Date) }
def to_date; end

sig { returns(::DateTime) }
def to_datetime; end

sig { params(form: T.nilable(Symbol)).returns(T.nilable(Time)) }
def to_time(form = :local); end
Comment on lines +412 to +413
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly, this one does return nil on error, unlike #to_date and #to_datetime.

"abc".to_time # => nil


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.anything]).returns(String) }
def truncate_words(words_count, options = {}); end

sig { returns(String) }
def underscore; end

sig { returns(String) }
def upcase_first; end
end