Skip to content

Commit d586a31

Browse files
apiologycastwidelekemula
authored
Allow levels to be changed for typechecking rules in .solargraph.yml (#1126)
* Add 'solargraph method_pin' command for debugging ```sh $ SOLARGRAPH_ASSERTS=on bundle exec solargraph method_pin --rbs 'RuboCop::AST::ArrayNode#values' def values: () -> Array $ bundle exec solargraph help method_pin Usage: solargraph method_pin [PATH] Options: [--rbs], [--no-rbs], [--skip-rbs] # Output the pin as RBS # Default: false [--typify], [--no-typify], [--skip-typify] # Output the calculated return type of the pin from annotations # Default: false [--probe], [--no-probe], [--skip-probe] # Output the calculated return type of the pin from annotations and inference # Default: false [--stack], [--no-stack], [--skip-stack] # Show entire stack by including definitions in superclasses # Default: false Describe a method pin $ ``` * RuboCop and Solargraph fixes * Linting fix * Add spec * Fix spec * Allow newer RBS gem versions, exclude incompatible ones (#995) * Allow newer RBS gem versions This allow users to upgrade to recent Tapioca versions. Tapioca now requires newish versions of the spoom gem, which depends on 4.x pre-releases of the rbs gem. * Add RBS version to test matrix * Add exclude rule * Move to 3.6.1 * Look for external requires before cataloging bench (#1021) * Look for external requires before cataloging bench It seems like sync_catalog will go through the motions but not actually load pins from gems here due to passing an empty requires array to ApiMap. I'm sure those requires get pulled in eventually, but we go through at least one catalog cycle without it happening. Found while trying to test a different issue but not being able to get completions from a gem in a spec. * Ensure backport is pre-cached * Remove Library#folding_ranges (#904) * Complain in strong type-checking if an @sg-ignore line is not needed (#1011) * Complain in strong type-checking if an @sg-ignore line is not needed * Fix return type * Fix spec * Linting/coverage fixes * Coverage fix * Document a log level env variable (#894) * Document a log level env variable * Fix logger reference * Fix env var name * Populate location information from RBS files (#768) * Populate location information from RBS files The 'rbs' gem maps the location of different definitions to the relevant point in the RGS files themselves - this change provides the ability to jump into the right place in those files to see the type definition via the LSP. * Prefer source location in language server * Resolve merge issue * Fix Path vs String type error * Consolidate parameter handling into Pin::Callable (#844) * Consolidate parameter handling into Pin::Closure * Clarify clobbered variable names * Fix bug in to_rbs, add spec, then fix new bug found after running spec * Catch one more Signature.new to translate from strict typechecking * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Introduce Pin::Callable * Use Pin::Callable type in args_node.rb * Select String#each_line overload with mandatory vs optional arg info * Adjust local variable presence to start after assignment, not before (#864) * Adjust local variable presence to start after assignment, not before * Add regression test around assignment in return position * Fix assignment visibility code, which relied on bad asgn semantics * Resolve params from ref tags (#872) * Resolve params from ref tags * Resolve ref tags with namespaces * Fix merge issue * RuboCop fixes * Add @sg-ignore * Linting * Linting fix * Linting fix --------- Co-authored-by: Fred Snyder <[email protected]> * Fix hole in type checking evaluation (#1009) * Fix hole in type checking evaluation The call to `bar()` in `[ bar('string') ].compact` is not currently type-checked due to a missed spot in `NodeMethods#call_nodes_from(node)` * Avoid over-reporting call issues * Fix annotation * Add nocov markers around unreachable area * Fix a coverage issue * Cover more paths in type checking * Fix a code coverage issue * Drop blank line * Ratchet Rubocop todo * Fix missing coverage * Improve typechecking error message (#1014) If we know the target of an unresolved method call, include it in the error message. * Internal strict type-checking fixes (#1013) * Internal strict type-checking fixes * Add annotation * Add annotation * Add @sg-ignores for now * Reproduce and fix a ||= (or-asgn) evaluation issue (#1017) * Reproduce and fix a ||= (or-asgn) evaluation issue * Fix linting issue * Define closure for Pin::Symbol, for completeness (#1027) This isn't used anywhere to my knowledge, but it makes sense to think of symbols as being in the global namespace, helps guarantee that closure is always available on a pin, and of course keeps the assert happy ;) * Fix 'all!' config to reporters (#1018) * Fix 'all!' config to reporters Solargraph found the type error here! * Linting fixes * Fix DocMap.all_rbs_collection_gems_in_memory return type (#1037) * Fix RuboCop linting errors in regular expressions (#1038) * Fix RuboCop linting errors in regular expressions * Continue on rubocop_todo errors * Move configuration * Continue on undercover errors * Resolve class aliases via Constant pins (#1029) * Resolve class aliases via Constant pins This also eliminates the need for Parser::NodeMethods as a searately defined class. * Resolve merge issues * Resolve Solargraph strong complaint * Add @sg-ignore * Fix RuboCop issues * Drop unused method * Ratchet .rubocop_todo.yml * Speed-up LSP completion response times (#1035) * Improve performance of resolve_method_aliases method - Add indexed lookups for methods and aliases by name - Cache ancestor traversal to avoid repeated computations - Separate regular pins from aliases for more efficient processing - Replace linear search with direct indexed method lookup - Add fast path for creating resolved alias pins without individual lookups Generated with Claude Code * Update .rubocop_todo.yml * Fix typechecking - get_method_stack order `get_method_stack` returns the following order for `Enumerable#select`: - master branch: => ["Enumerable#select", "Kernel#select"] - current branch: => ["Kernel#select", "Enumerable#select"] * Avoid redundant indexing methods_by_name loop through ancestors and rely on store.get_path_pins * RuboCop todo update * Try rbs collection update before specs * RuboCop fixes * Add @sg-ignores * Fix typo * Exclude problematic combinations on Ruby head * Fix indentation * method_pin -> pin, hide command * Fix type * RuboCop todo file stability To avoid merge conflicts and contributors having to deal with non-intuitive RuboCop todo changes: * Lock down development versions of RuboCop and plugins so that unrelated PRs aren't affected by newly implemented RuboCop rules. * Exclude rule entirely if more than 5 files violate it today, so that PRs are less likely to cause todo file changes unless they are specifically targeted at cleanup. * Clarify guidance on RuboCop todo file in CI error message. * Fix to hopefully ensure guidance always appears in CI error message. * Fix merge issue * Add --references flag (superclass for now) * Add another @sg-ignore * Catch up with .rubocop_todo.yml * Add another @sg-ignore * Tolerate case statement in specs * Rerun rubocop todo * Force build * Restore * install -> update with rbs collection * Try Ruby 3.2 * Update solargraph * Re-add bundle install * Drop MATRIX_SOLARGRAPH_VERSION * Drop debugging changes * Update expectations from master branch * Update rubocop todo * Fix merge failure * Allow more valid method pin paths * RuboCop fix * Linting * Reproduce solargraph-rspec rspec failure * Trigger build * Fix new bundler issue * Debug * Turn off warning diagnostics in CLI * Debug * Fix pin merging bug * Add @sg-ignore * Allow levels to be changed for typechecking rules in .solargraph.yml Suggested updates for https://solargraph.org/guides/configuration: 1) Change block in Defaults section to: ``` include: - "**/*.rb" exclude: - spec/**/* - test/**/* - vendor/**/* - ".bundle/**/*" require: [] domains: [] reporters: - rubocop - require_not_found formatter: rubocop: cops: safe except: [] only: [] extra_args: [] type_checker: rules: {} max_files: 5000 ``` 2) Add section 'formatter' (hadn't been added) Specify configuration for code formatters used in the LSP. Currently supports 'rubocop' as the subsection, with the following keys: * `cops`: Valid values: 'all', 'safe' * `except`: List of cops to pass to the --except argument in the RuboCop CLI * `only`: List of cops to pass to the --only argument in the RuboCop CLI * `extra_args`: Additional arguments to pass to the RuboCop CLI 3) Add section 'type_checker.rules' Override which rules are applied at a given typechecking level. Example: ``` type_checker: rules: validate_calls: typed # make 'typed' level more strict without # bringing in other rules ``` For the current list of typechecking rules, see [rules.rb](https://github.com/castwide/solargraph/blob/master/lib/solargraph/type_checker/rules.rb) in the Solargraph source. * Fix merge * Fix merge --------- Co-authored-by: Fred Snyder <[email protected]> Co-authored-by: Lekë Mula <[email protected]>
1 parent 22455db commit d586a31

File tree

7 files changed

+63
-19
lines changed

7 files changed

+63
-19
lines changed

lib/solargraph/shell.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ def reporters
173173
# @return [void]
174174
def typecheck *files
175175
directory = File.realpath(options[:directory])
176+
workspace = Solargraph::Workspace.new(directory)
177+
level = options[:level].to_sym
178+
rules = workspace.rules(level)
176179
api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout)
177180
probcount = 0
178181
if files.empty?
@@ -184,7 +187,7 @@ def typecheck *files
184187

185188
time = Benchmark.measure {
186189
files.each do |file|
187-
checker = TypeChecker.new(file, api_map: api_map, level: options[:level].to_sym)
190+
checker = TypeChecker.new(file, api_map: api_map, level: options[:level].to_sym, workspace: workspace)
188191
problems = checker.problems
189192
next if problems.empty?
190193
problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line }

lib/solargraph/source.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ class Source
1515

1616
include EncodingFixes
1717

18-
# @return [String]
18+
# @return [String, nil]
1919
attr_reader :filename
2020

2121
# @return [String]

lib/solargraph/type_checker.rb

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,23 @@ class TypeChecker
2121
# @return [ApiMap]
2222
attr_reader :api_map
2323

24-
# @param filename [String]
24+
# @param filename [String, nil]
2525
# @param api_map [ApiMap, nil]
26-
# @param level [Symbol]
27-
def initialize filename, api_map: nil, level: :normal
26+
# @param level [Symbol] Don't complain about anything above this level
27+
# @param workspace [Workspace, nil] Workspace to use for loading
28+
# type checker rules modified by user config
29+
# @param type_checker_rules [Hash{Symbol => Symbol}] Overrides for
30+
# type checker rules - e.g., :report_undefined => :strong
31+
# @param rules [Rules] Type checker rules object
32+
def initialize filename,
33+
api_map: nil,
34+
level: :normal,
35+
workspace: filename ? Workspace.new(File.dirname(filename)) : nil,
36+
rules: workspace ? workspace.rules(level) : Rules.new(level, {})
2837
@filename = filename
2938
# @todo Smarter directory resolution
3039
@api_map = api_map || Solargraph::ApiMap.load(File.dirname(filename))
31-
@rules = Rules.new(level)
40+
@rules = rules
3241
# @type [Array<Range>]
3342
@marked_ranges = []
3443
end

lib/solargraph/type_checker/rules.rb

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,50 +20,64 @@ class Rules
2020
attr_reader :rank
2121

2222
# @param level [Symbol]
23-
def initialize level
23+
# @param overrides [Hash{Symbol => Symbol}]
24+
def initialize level, overrides
2425
@rank = if LEVELS.key?(level)
2526
LEVELS[level]
2627
else
2728
Solargraph.logger.warn "Unrecognized TypeChecker level #{level}, assuming normal"
2829
0
2930
end
3031
@level = LEVELS[LEVELS.values.index(@rank)]
32+
@overrides = overrides
3133
end
3234

3335
def ignore_all_undefined?
34-
rank < LEVELS[:strict]
36+
!report_undefined?
37+
end
38+
39+
def report_undefined?
40+
report?(:report_undefined, :strict)
3541
end
3642

3743
def validate_consts?
38-
rank >= LEVELS[:strict]
44+
report?(:validate_consts, :strict)
3945
end
4046

4147
def validate_calls?
42-
rank >= LEVELS[:strict]
48+
report?(:validate_calls, :strict)
4349
end
4450

4551
def require_type_tags?
46-
rank >= LEVELS[:strong]
52+
report?(:validate_type_tags, :strong)
4753
end
4854

4955
def must_tag_or_infer?
50-
rank > LEVELS[:typed]
56+
report?(:must_tag_or_infer, :strict)
5157
end
5258

5359
def validate_tags?
54-
rank > LEVELS[:normal]
60+
report?(:validate_tags, :typed)
5561
end
5662

5763
def require_all_return_types_match_inferred?
58-
rank >= LEVELS[:alpha]
64+
report?(:require_all_return_types_match_inferred, :alpha)
5965
end
6066

6167
# We keep this at strong because if you added an @ sg-ignore to
6268
# address a strong-level issue, then ran at a lower level, you'd
6369
# get a false positive - we don't run stronger level checks than
6470
# requested for performance reasons
6571
def validate_sg_ignores?
66-
rank >= LEVELS[:strong]
72+
report?(:validate_sg_ignores, :strong)
73+
end
74+
75+
private
76+
77+
# @param type [Symbol]
78+
# @param level [Symbol]
79+
def report?(type, level)
80+
rank >= LEVELS[@overrides.fetch(type, level)]
6781
end
6882
end
6983
end

lib/solargraph/workspace.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ def config
5050
@config ||= Solargraph::Workspace::Config.new(directory)
5151
end
5252

53+
# @param level [Symbol]
54+
# @return [TypeChecker::Rules]
55+
def rules(level)
56+
@rules ||= TypeChecker::Rules.new(level, config.type_checker_rules)
57+
end
58+
5359
# Merge the source. A merge will update the existing source for the file
5460
# or add it to the sources if the workspace is configured to include it.
5561
# The source is ignored if the configuration excludes it.

lib/solargraph/workspace/config.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ def max_files
115115
raw_data['max_files']
116116
end
117117

118+
# @return [Hash{Symbol => Symbol}]
119+
def type_checker_rules
120+
# @type [Hash{String => String}]
121+
raw_rules = raw_data.fetch('type_checker', {}).fetch('rules', {})
122+
raw_rules.to_h do |k, v|
123+
[k.to_sym, v.to_sym]
124+
end
125+
end
126+
118127
private
119128

120129
# @return [String]
@@ -168,6 +177,9 @@ def default_config
168177
'extra_args' =>[]
169178
}
170179
},
180+
'type_checker' => {
181+
'rules' => { }
182+
},
171183
'require_paths' => [],
172184
'plugins' => [],
173185
'max_files' => MAX_FILES

spec/type_checker/rules_spec.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
describe Solargraph::TypeChecker::Rules do
22
it 'sets normal rules' do
3-
rules = Solargraph::TypeChecker::Rules.new(:normal)
3+
rules = Solargraph::TypeChecker::Rules.new(:normal, {})
44
expect(rules.ignore_all_undefined?).to be(true)
55
expect(rules.must_tag_or_infer?).to be(false)
66
expect(rules.require_type_tags?).to be(false)
@@ -9,7 +9,7 @@
99
end
1010

1111
it 'sets typed rules' do
12-
rules = Solargraph::TypeChecker::Rules.new(:typed)
12+
rules = Solargraph::TypeChecker::Rules.new(:typed, {})
1313
expect(rules.ignore_all_undefined?).to be(true)
1414
expect(rules.must_tag_or_infer?).to be(false)
1515
expect(rules.require_type_tags?).to be(false)
@@ -18,7 +18,7 @@
1818
end
1919

2020
it 'sets strict rules' do
21-
rules = Solargraph::TypeChecker::Rules.new(:strict)
21+
rules = Solargraph::TypeChecker::Rules.new(:strict, {})
2222
expect(rules.ignore_all_undefined?).to be(false)
2323
expect(rules.must_tag_or_infer?).to be(true)
2424
expect(rules.require_type_tags?).to be(false)
@@ -27,7 +27,7 @@
2727
end
2828

2929
it 'sets strong rules' do
30-
rules = Solargraph::TypeChecker::Rules.new(:strong)
30+
rules = Solargraph::TypeChecker::Rules.new(:strong, {})
3131
expect(rules.ignore_all_undefined?).to be(false)
3232
expect(rules.must_tag_or_infer?).to be(true)
3333
expect(rules.require_type_tags?).to be(true)

0 commit comments

Comments
 (0)