class RuboCop::Cop::Sorbet::ValidSigil
Otherwise, if a ‘MinimumStrictness` level is specified, it will be used in offense messages and autocorrect.
If a `SuggestedStrictness` level is specified, it will be used in autocorrect.
If an `ExactStrictness` level is specified, it will be used in offense messages and autocorrect.
* `ExactStrictness`: If set, make offense if the strictness level in the file is different than this one
* `MinimumStrictness`: If set, make offense if the strictness level in the file is below this one
* `SuggestedStrictness`: Sorbet strictness level suggested in offense messages (default: ’false’)
* ‘RequireSigilOnAllFiles`: make offense if the Sorbet typed is not found in the file (default: false)
Options:
Adapted from: gist.github.com/clarkdave/85aca4e16f33fd52aceb6a0a29936e52<br>Checks that every Ruby file contains a valid Sorbet sigil.
def autocorrect(corrector)
def autocorrect(corrector) return unless require_sigil_on_all_files? return unless extract_sigil(processed_source).nil? token = processed_source.tokens.first replace_with = suggested_strictness_level sigil = "# typed: #{replace_with}" if token.text.start_with?("#!") # shebang line corrector.insert_after(token.pos, "\n#{sigil}") else corrector.insert_before(token.pos, "#{sigil}\n") end end
def check_double_commented_sigil(sigil, strictness)
def check_double_commented_sigil(sigil, strictness) return unless sigil.text.start_with?(/[[:blank:]]*#[[:blank:]]*#/) add_offense( sigil.pos, message: format(INVALID_SIGIL_MSG, sigil: sigil.text), ) do |corrector| # Remove the extra comment and normalize spaces corrector.replace( sigil.pos, "# typed: #{strictness}", ) end end
def check_sigil_present(sigil)
def check_sigil_present(sigil) return true unless sigil.nil? token = processed_source.tokens.first if require_sigil_on_all_files? strictness = suggested_strictness_level add_offense( token.pos, message: "No Sorbet sigil found in file. " \ "Try a `typed: #{strictness}` to start (you can also use `rubocop -a` to automatically add this).", ) do |corrector| autocorrect(corrector) end end false end
def check_strictness_level(sigil, strictness)
def check_strictness_level(sigil, strictness) return true if !minimum_strictness && !exact_strictness current_level = STRICTNESS_LEVELS.index(strictness) if exact_strictness exact_level = STRICTNESS_LEVELS.index(exact_strictness) if current_level != exact_level add_offense( sigil.pos, message: "Sorbet sigil should be `#{exact_strictness}` got `#{strictness}`.", ) do |corrector| autocorrect(corrector) end return false end else minimum_level = STRICTNESS_LEVELS.index(minimum_strictness) if current_level < minimum_level add_offense( sigil.pos, message: "Sorbet sigil should be at least `#{minimum_strictness}` got `#{strictness}`.", ) do |corrector| autocorrect(corrector) end return false end end true end
def check_strictness_not_empty(sigil, strictness)
def check_strictness_not_empty(sigil, strictness) return true if strictness add_offense( sigil.pos, message: "Sorbet sigil should not be empty.", ) do |corrector| autocorrect(corrector) end false end
def check_strictness_valid(sigil, strictness)
def check_strictness_valid(sigil, strictness) return true if STRICTNESS_LEVELS.include?(strictness) add_offense( sigil.pos, message: format(INVALID_SIGIL_MSG, sigil: strictness), ) do |corrector| autocorrect(corrector) end false end
def exact_strictness
def exact_strictness config = cop_config["ExactStrictness"].to_s config if STRICTNESS_LEVELS.include?(config) end
def extract_sigil(processed_source)
def extract_sigil(processed_source) processed_source.tokens .take_while { |token| token.type == :tCOMMENT } .find { |token| SIGIL_REGEX.match?(token.text) } end
def extract_strictness(sigil)
def extract_strictness(sigil) sigil.text.match(SIGIL_REGEX)&.captures&.first end
def minimum_strictness
def minimum_strictness config = cop_config["MinimumStrictness"].to_s config if STRICTNESS_LEVELS.include?(config) end
def on_new_investigation
def on_new_investigation return if processed_source.tokens.empty? sigil = extract_sigil(processed_source) return unless check_sigil_present(sigil) strictness = extract_strictness(sigil) check_double_commented_sigil(sigil, strictness) return unless check_strictness_not_empty(sigil, strictness) return unless check_strictness_valid(sigil, strictness) nil unless check_strictness_level(sigil, strictness) end
def require_sigil_on_all_files?
def require_sigil_on_all_files? !!cop_config["RequireSigilOnAllFiles"] end
def suggested_strictness
def suggested_strictness config = cop_config["SuggestedStrictness"].to_s STRICTNESS_LEVELS.include?(config) ? config : "false" end
def suggested_strictness_level
def suggested_strictness_level return exact_strictness if exact_strictness # if no minimum strictness is set (eg. using Sorbet/HasSigil without config) then # we always use the suggested strictness which defaults to `false` return suggested_strictness unless minimum_strictness # special case: if you're using Sorbet/IgnoreSigil without config, we should recommend `ignore` return "ignore" if minimum_strictness == "ignore" && cop_config["SuggestedStrictness"].nil? # if a minimum strictness is set (eg. you're using Sorbet/FalseSigil) # we want to compare the minimum strictness and suggested strictness. this is because # the suggested strictness might be higher than the minimum (eg. if you want all new files # at a higher strictness level, without having to migrate existing files at lower levels). levels = [ STRICTNESS_LEVELS.index(suggested_strictness), STRICTNESS_LEVELS.index(minimum_strictness), ] STRICTNESS_LEVELS[levels.compact.max] end