diff --git a/.github/workflows/plugins.yml b/.github/workflows/plugins.yml index 4556215f1..3de7288eb 100644 --- a/.github/workflows/plugins.yml +++ b/.github/workflows/plugins.yml @@ -118,9 +118,12 @@ jobs: - name: clone https://github.com/lekemula/solargraph-rspec/ run: | cd .. - git clone https://github.com/lekemula/solargraph-rspec.git + # git clone https://github.com/lekemula/solargraph-rspec.git + # pending https://github.com/lekemula/solargraph-rspec/pull/31 + git clone https://github.com/apiology/solargraph-rspec.git cd solargraph-rspec + git checkout test_solargraph_prereleases - name: Set up Ruby uses: ruby/setup-ruby@v1 with: diff --git a/.github/workflows/rspec.yml b/.github/workflows/rspec.yml index ad0d5c20b..174a1a6e3 100644 --- a/.github/workflows/rspec.yml +++ b/.github/workflows/rspec.yml @@ -70,11 +70,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} - # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 - # - # match version in Gemfile.lock and use same version below - bundler: 2.5.23 - bundler-cache: false + bundler-cache: true - name: Set rbs version run: echo "gem 'rbs', '${{ matrix.rbs-version }}'" >> .Gemfile # /home/runner/.rubies/ruby-head/lib/ruby/gems/3.5.0+2/gems/rbs-3.9.4/lib/rbs.rb:11: @@ -83,9 +79,8 @@ jobs: # starting from Ruby 3.6.0 - name: Work around legacy rbs deprecation on ruby > 3.4 run: echo "gem 'tsort'" >> .Gemfile - - name: Install gems + - name: Update gems run: | - bundle _2.5.23_ install bundle update rbs # use latest available for this Ruby version - name: Update types run: bundle exec rbs collection update @@ -103,13 +98,7 @@ jobs: uses: ruby/setup-ruby@v1 with: ruby-version: '3.4' - # see https://github.com/castwide/solargraph/actions/runs/19391419903/job/55485410493?pr=1119 - # - # match version in Gemfile.lock and use same version below - bundler: 2.5.23 - bundler-cache: false - - name: Install gems - run: bundle install + bundler-cache: true - name: Update types run: bundle exec rbs collection update - name: Run tests diff --git a/.gitignore b/.gitignore index 2819165b1..75510d96b 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,4 @@ coverage /Makefile /.pryrc /.rspec-local +vendor/cache diff --git a/.rubocop.yml b/.rubocop.yml index 51b022f51..96ec6c6b2 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -21,6 +21,25 @@ AllCops: - "vendor/**/.*" TargetRubyVersion: 3.0 +Gemspec/RequiredRubyVersion: + Exclude: + - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' + - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' + - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' + +Gemspec/DevelopmentDependencies: + EnforcedStyle: gemspec + Exclude: + - 'spec/fixtures/**/*' + +Lint/EmptyFile: + Exclude: + - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' + +Naming/VariableName: + Exclude: + - 'spec/fixtures/unicode.rb' + # We don't use the spec/solargraph directory RSpec/SpecFilePathFormat: Enabled: false @@ -31,6 +50,14 @@ Style/MethodDefParentheses: Layout/EmptyLineAfterGuardClause: Enabled: false +Naming/AsciiIdentifiers: + Exclude: + - 'spec/fixtures/unicode.rb' + +Lint/EmptyClass: + Exclude: + - spec/fixtures/**/*.rb + Lint/UnusedMethodArgument: AllowUnusedKeywordArguments: true @@ -48,17 +75,17 @@ Style/ClassVars: # improve existing code! # Metrics/AbcSize: - Max: 65 + Max: 110 Metrics/MethodLength: - Max: 60 + Max: 70 Metrics/ClassLength: Max: 500 Metrics/CyclomaticComplexity: - Max: 23 + Max: 40 Metrics/PerceivedComplexity: - Max: 29 + Max: 40 RSpec/ExampleLength: - Max: 17 + Max: 310 plugins: - rubocop-rspec diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 0616ea8d2..eea514d1c 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -6,30 +6,12 @@ # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# This cop supports safe autocorrection (--autocorrect). -Gemspec/AddRuntimeDependency: - Exclude: - - 'solargraph.gemspec' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Severity. Gemspec/DeprecatedAttributeAssignment: Exclude: - - 'solargraph.gemspec' - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' -# Configuration parameters: EnforcedStyle, AllowedGems. -# SupportedStyles: Gemfile, gems.rb, gemspec -Gemspec/DevelopmentDependencies: - Exclude: - - 'solargraph.gemspec' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: TreatCommentsAsGroupSeparators, ConsiderPunctuation. -Gemspec/OrderedDependencies: - Exclude: - - 'solargraph.gemspec' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Severity. Gemspec/RequireMFA: @@ -37,260 +19,18 @@ Gemspec/RequireMFA: - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' -# Configuration parameters: Severity. -Gemspec/RequiredRubyVersion: - Exclude: - - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' - - 'spec/fixtures/rubocop-custom-version/specifications/rubocop-0.0.0.gemspec' - - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: with_first_argument, with_fixed_indentation -Layout/ArgumentAlignment: - Exclude: - - 'lib/solargraph/pin/callable.rb' - - 'spec/source/source_chainer_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleAlignWith. -# SupportedStylesAlignWith: either, start_of_block, start_of_line -Layout/BlockAlignment: - Exclude: - - 'spec/source_map/mapper_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/ClosingHeredocIndentation: - Exclude: - - 'spec/diagnostics/rubocop_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment. -Layout/CommentIndentation: - Exclude: - - 'lib/solargraph/language_server/host.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/source_map/mapper.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/ElseAlignment: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EmptyLineBetweenMethodDefs, EmptyLineBetweenClassDefs, EmptyLineBetweenModuleDefs, DefLikeMacros, AllowAdjacentOneLineDefs, NumberOfEmptyLines. -Layout/EmptyLineBetweenDefs: - Exclude: - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/pin/delegated_method.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/EmptyLines: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: empty_lines, empty_lines_except_namespace, empty_lines_special, no_empty_lines -Layout/EmptyLinesAroundModuleBody: - Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyleAlignWith, Severity. # SupportedStylesAlignWith: keyword, variable, start_of_line Layout/EndAlignment: - Enabled: false - -# Configuration parameters: EnforcedStyle. -# SupportedStyles: native, lf, crlf -Layout/EndOfLine: - Exclude: - - 'Gemfile' - - 'Rakefile' - - 'solargraph.gemspec' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment. -Layout/ExtraSpacing: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/type_checker.rb' - - 'spec/spec_helper.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: consistent, consistent_relative_to_receiver, special_for_inner_method_call, special_for_inner_method_call_in_parentheses -Layout/FirstArgumentIndentation: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/args_node.rb' - - 'spec/source/source_chainer_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_brackets -Layout/FirstArrayElementIndentation: - Exclude: - - 'lib/solargraph/source.rb' - - 'spec/diagnostics/update_errors_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - - 'spec/source_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: special_inside_parentheses, consistent, align_braces -Layout/FirstHashElementIndentation: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle. -# SupportedHashRocketStyles: key, separator, table -# SupportedColonStyles: key, separator, table -# SupportedLastArgumentHashStyles: always_inspect, always_ignore, ignore_implicit, ignore_explicit -Layout/HashAlignment: - Exclude: - - 'lib/solargraph/workspace/config.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/HeredocIndentation: Exclude: - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/yard_map/mapper/to_method_spec.rb' + - 'lib/solargraph/shell.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: Width, AllowedPatterns. Layout/IndentationWidth: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowDoxygenCommentStyle, AllowGemfileRubyComment, AllowRBSInlineAnnotation, AllowSteepAnnotation. -Layout/LeadingCommentSpace: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source_map/clip.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/LineContinuationSpacing: - Exclude: - - 'lib/solargraph/diagnostics/rubocop_helpers.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: symmetrical, new_line, same_line -Layout/MultilineMethodCallBraceLayout: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'spec/source/source_chainer_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented, indented_relative_to_receiver -Layout/MultilineMethodCallIndentation: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, IndentationWidth. -# SupportedStyles: aligned, indented -Layout/MultilineOperationIndentation: - Exclude: - - 'lib/solargraph/language_server/host/dispatch.rb' - - 'lib/solargraph/source.rb' - -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceAfterComma: - Exclude: - - 'spec/source/cursor_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, no_space -Layout/SpaceAroundEqualsInParameterDefault: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator, EnforcedStyleForRationalLiterals. -# SupportedStylesForExponentOperator: space, no_space -# SupportedStylesForRationalLiterals: space, no_space -Layout/SpaceAroundOperators: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceBeforeBlockBraces: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Layout/SpaceBeforeComma: - Exclude: - - 'spec/source/cursor_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. -# SupportedStyles: space, no_space -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideBlockBraces: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBraces: space, no_space -Layout/SpaceInsideHashLiteralBraces: - Exclude: - - 'lib/solargraph/language_server/message/extended/search.rb' - - 'lib/solargraph/language_server/message/initialize.rb' - - 'lib/solargraph/workspace/config.rb' - - 'spec/language_server/host/message_worker_spec.rb' - - 'spec/language_server/message/extended/check_gem_version_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: space, compact, no_space -Layout/SpaceInsideParens: - Exclude: - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/source_map.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowInHeredoc. -Layout/TrailingWhitespace: - Exclude: - - 'lib/solargraph/language_server/message/client/register_capability.rb' - - 'spec/api_map/config_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedMethods, AllowedPatterns. -Lint/AmbiguousBlockAssociation: - Exclude: - - 'lib/solargraph/language_server/host.rb' - -# This cop supports safe autocorrection (--autocorrect). -Lint/AmbiguousOperator: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Lint/AmbiguousOperatorPrecedence: - Exclude: - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/source.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: RequireParenthesesForMethodChains. -Lint/AmbiguousRange: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowSafeAssignment. -Lint/AssignmentInCondition: Exclude: - - 'lib/solargraph/library.rb' + - 'lib/solargraph/shell.rb' Lint/BinaryOperatorWithIdenticalOperands: Exclude: @@ -300,7 +40,6 @@ Lint/BinaryOperatorWithIdenticalOperands: Lint/BooleanSymbol: Exclude: - 'lib/solargraph/convention/struct_definition/struct_definition_node.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - 'lib/solargraph/source/chain/literal.rb' # Configuration parameters: AllowedMethods. @@ -312,110 +51,21 @@ Lint/ConstantDefinitionInBlock: # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch. Lint/DuplicateBranch: Exclude: - - 'lib/solargraph/complex_type/type_methods.rb' - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/rbs_map/conversions.rb' -Lint/DuplicateMethods: - Enabled: false - -# Configuration parameters: AllowComments. -Lint/EmptyClass: - Enabled: false - -# Configuration parameters: AllowComments. -Lint/EmptyFile: - Exclude: - - 'spec/fixtures/vendored/vendor/do_not_use.gemspec' - # This cop supports unsafe autocorrection (--autocorrect-all). Lint/InterpolationCheck: Exclude: - - 'spec/complex_type_spec.rb' - - 'spec/parser/node_methods_spec.rb' - 'spec/source/chain_spec.rb' - 'spec/source/cursor_spec.rb' -# Configuration parameters: AllowedParentClasses. -Lint/MissingSuper: - Exclude: - - 'lib/solargraph/source/chain/call.rb' - - 'lib/solargraph/source/chain/constant.rb' - - 'lib/solargraph/source/chain/if.rb' - - 'lib/solargraph/source/chain/literal.rb' - - 'lib/solargraph/source/chain/or.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Lint/NonAtomicFileOperation: - Exclude: - - 'spec/diagnostics/rubocop_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Lint/ParenthesesAsGroupedExpression: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'spec/language_server/host_spec.rb' - - 'spec/source_map/clip_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Lint/RedundantRequireStatement: - Exclude: - - 'spec/language_server/protocol_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedMethods, InferNonNilReceiver, AdditionalNilMethods. -# AllowedMethods: instance_of?, kind_of?, is_a?, eql?, respond_to?, equal? -# AdditionalNilMethods: present?, blank?, try, try! -Lint/RedundantSafeNavigation: - Exclude: - - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/rbs_map.rb' - -# This cop supports safe autocorrection (--autocorrect). -Lint/RedundantStringCoercion: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/pin/conversions.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/namespace.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - -# This cop supports safe autocorrection (--autocorrect). -Lint/RedundantWithIndex: - Exclude: - - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: strict, consistent -Lint/SymbolConversion: - Exclude: - - 'lib/solargraph/pin/base.rb' - # Configuration parameters: AllowKeywordBlockArguments. Lint/UnderscorePrefixedVariableName: Exclude: - 'lib/solargraph/library.rb' -# Configuration parameters: Methods. -Lint/UnexpectedBlockArity: - Exclude: - - 'lib/solargraph/language_server/message/completion_item/resolve.rb' - - 'lib/solargraph/type_checker.rb' - -Lint/UnmodifiedReduceAccumulator: - Exclude: - - 'lib/solargraph/pin/method.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: IgnoreEmptyBlocks, AllowUnusedKeywordArguments. -Lint/UnusedBlockArgument: - Exclude: - - 'lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb' - - 'lib/solargraph/logging.rb' - - 'spec/language_server/transport/data_reader_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions. # NotImplementedExceptions: NotImplementedError @@ -424,25 +74,25 @@ Lint/UnusedMethodArgument: # This cop supports safe autocorrection (--autocorrect). Lint/UselessAssignment: - Enabled: false - -Lint/UselessConstantScoping: Exclude: - - 'lib/solargraph/rbs_map/conversions.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Lint/UselessMethodDefinition: - Exclude: - - 'lib/solargraph/pin/signature.rb' + - 'lib/solargraph/pin/block.rb' + - 'spec/fixtures/long_squiggly_heredoc.rb' + - 'spec/fixtures/rubocop-unused-variable-error/app.rb' + - 'spec/fixtures/unicode.rb' # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max. Metrics/AbcSize: - Enabled: false + Exclude: + - 'lib/solargraph/api_map/source_to_yard.rb' + - 'lib/solargraph/parser/parser_gem/node_chainer.rb' + - 'lib/solargraph/source/source_chainer.rb' + - 'lib/solargraph/source_map/clip.rb' + - 'lib/solargraph/source_map/mapper.rb' # Configuration parameters: CountComments, CountAsOne, AllowedMethods, AllowedPatterns, inherit_mode. # AllowedMethods: refine Metrics/BlockLength: - Max: 57 + Max: 61 # Configuration parameters: CountBlocks, CountModifierForms. Metrics/BlockNesting: @@ -459,7 +109,8 @@ Metrics/ClassLength: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/CyclomaticComplexity: - Enabled: false + Exclude: + - 'lib/solargraph/type_checker.rb' # Configuration parameters: CountComments, Max, CountAsOne, AllowedMethods, AllowedPatterns. Metrics/MethodLength: @@ -467,7 +118,7 @@ Metrics/MethodLength: # Configuration parameters: CountComments, CountAsOne. Metrics/ModuleLength: - Max: 169 + Max: 167 # Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters. Metrics/ParameterLists: @@ -480,7 +131,9 @@ Metrics/ParameterLists: # Configuration parameters: AllowedMethods, AllowedPatterns, Max. Metrics/PerceivedComplexity: - Enabled: false + Exclude: + - 'lib/solargraph/parser/parser_gem/node_chainer.rb' + - 'lib/solargraph/type_checker.rb' Naming/AccessorMethodName: Exclude: @@ -488,29 +141,17 @@ Naming/AccessorMethodName: - 'lib/solargraph/api_map/store.rb' - 'lib/solargraph/language_server/message/base.rb' -# Configuration parameters: AsciiConstants. -Naming/AsciiIdentifiers: - Exclude: - - 'spec/fixtures/unicode.rb' - # Configuration parameters: ForbiddenDelimiters. # ForbiddenDelimiters: (?i-mx:(^|\s)(EO[A-Z]{1}|END)(\s|$)) Naming/HeredocDelimiterNaming: Exclude: - 'spec/yard_map/mapper/to_method_spec.rb' -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyleForLeadingUnderscores. -# SupportedStylesForLeadingUnderscores: disallowed, required, optional -Naming/MemoizedInstanceVariableName: - Enabled: false - # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames. # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to Naming/MethodParameterName: Exclude: - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/pin/base.rb' - 'lib/solargraph/range.rb' - 'lib/solargraph/source.rb' - 'lib/solargraph/yard_map/mapper/to_method.rb' @@ -530,36 +171,10 @@ Naming/PredicatePrefix: Exclude: - 'spec/**/*' - 'lib/solargraph/api_map.rb' - - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/parser/parser_gem/class_methods.rb' - 'lib/solargraph/pin_cache.rb' - 'lib/solargraph/workspace.rb' -# Configuration parameters: EnforcedStyle, AllowedIdentifiers, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns. -# SupportedStyles: snake_case, camelCase -Naming/VariableName: - Exclude: - - 'spec/fixtures/unicode.rb' - -RSpec/Be: - Exclude: - - 'spec/rbs_map/stdlib_map_spec.rb' - - 'spec/source/source_chainer_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/BeEq: - Exclude: - - 'spec/complex_type_spec.rb' - - 'spec/pin/method_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: be, be_nil -RSpec/BeNil: - Exclude: - - 'spec/api_map/source_to_yard_spec.rb' - - 'spec/language_server/host_spec.rb' - RSpec/BeforeAfterAll: Exclude: - '**/spec/spec_helper.rb' @@ -569,11 +184,6 @@ RSpec/BeforeAfterAll: - 'spec/language_server/host/dispatch_spec.rb' - 'spec/language_server/protocol_spec.rb' -# Configuration parameters: Prefixes, AllowedPatterns. -# Prefixes: when, with, without -RSpec/ContextWording: - Enabled: false - # Configuration parameters: IgnoredMetadata. RSpec/DescribeClass: Exclude: @@ -582,37 +192,7 @@ RSpec/DescribeClass: - '**/spec/routing/**/*' - '**/spec/system/**/*' - '**/spec/views/**/*' - - 'spec/api_map_method_spec.rb' - 'spec/complex_type_spec.rb' - - 'spec/source_map/node_processor_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: SkipBlocks, EnforcedStyle, OnlyStaticConstants. -# SupportedStyles: described_class, explicit -RSpec/DescribedClass: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -RSpec/EmptyLineAfterFinalLet: - Exclude: - - 'spec/workspace/config_spec.rb' - -# Configuration parameters: Max, CountAsOne. -RSpec/ExampleLength: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: CustomTransform, IgnoredWords, DisallowedExamples. -# DisallowedExamples: works -RSpec/ExampleWording: - Exclude: - - 'spec/pin/base_spec.rb' - - 'spec/pin/method_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -RSpec/ExcessiveDocstringSpacing: - Exclude: - - 'spec/source/chain/call_spec.rb' # This cop supports safe autocorrection (--autocorrect). RSpec/ExpectActual: @@ -621,12 +201,6 @@ RSpec/ExpectActual: - 'spec/rbs_map/stdlib_map_spec.rb' - 'spec/source_map/mapper_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, each, example -RSpec/HookArgument: - Enabled: false - # Configuration parameters: AssignmentOnly. RSpec/InstanceVariable: Enabled: false @@ -635,15 +209,6 @@ RSpec/LeakyConstantDeclaration: Exclude: - 'spec/complex_type_spec.rb' -# This cop supports safe autocorrection (--autocorrect). -RSpec/LetBeforeExamples: - Exclude: - - 'spec/complex_type_spec.rb' - -RSpec/MissingExampleGroupArgument: - Exclude: - - 'spec/diagnostics/rubocop_helpers_spec.rb' - RSpec/MultipleExpectations: Max: 14 @@ -651,64 +216,10 @@ RSpec/MultipleExpectations: RSpec/NestedGroups: Max: 4 -# Configuration parameters: AllowedPatterns. -# AllowedPatterns: ^expect_, ^assert_ -RSpec/NoExpectationExample: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: not_to, to_not -RSpec/NotToNot: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/rbs_map/core_map_spec.rb' - -RSpec/PendingWithoutReason: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/doc_map_spec.rb' - - 'spec/pin/base_variable_spec.rb' - - 'spec/pin/local_variable_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Strict, EnforcedStyle, AllowedExplicitMatchers. -# SupportedStyles: inflected, explicit -RSpec/PredicateMatcher: - Exclude: - - 'spec/language_server/message/workspace/did_change_configuration_spec.rb' - - 'spec/source_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -RSpec/ReceiveMessages: - Exclude: - - 'spec/language_server/host_spec.rb' - RSpec/RemoveConst: Exclude: - 'spec/diagnostics/rubocop_helpers_spec.rb' -RSpec/RepeatedDescription: - Enabled: false - -RSpec/RepeatedExample: - Exclude: - - 'spec/api_map_spec.rb' - - 'spec/parser/node_methods_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source_map/clip_spec.rb' - - 'spec/type_checker/levels/strict_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -RSpec/ScatteredLet: - Exclude: - - 'spec/complex_type_spec.rb' - -# Configuration parameters: IgnoreNameless, IgnoreSymbolicNames. -RSpec/VerifiedDoubles: - Enabled: false - Security/MarshalLoad: Exclude: - 'lib/solargraph/pin_cache.rb' @@ -717,23 +228,8 @@ Security/MarshalLoad: # Configuration parameters: EnforcedStyle, AllowModifiersOnSymbols, AllowModifiersOnAttrs, AllowModifiersOnAliasMethod. # SupportedStyles: inline, group Style/AccessModifierDeclarations: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: separated, grouped -Style/AccessorGrouping: Exclude: - - 'lib/solargraph/parser/flow_sensitive_typing.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/rbs_map.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: always, conditionals -Style/AndOr: - Enabled: false + - 'lib/solargraph/source/chain/literal.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: AllowOnlyRestArgument, UseAnonymousForwarding, RedundantRestArgumentNames, RedundantKeywordRestArgumentNames, RedundantBlockArgumentNames. @@ -751,49 +247,8 @@ Style/ArgumentsForwarding: # FunctionalMethods: let, let!, subject, watch # AllowedMethods: lambda, proc, it Style/BlockDelimiters: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: MinBranchesCount. -Style/CaseLikeIf: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle, EnforcedStyleForClasses, EnforcedStyleForModules. -# SupportedStyles: nested, compact -# SupportedStylesForClasses: ~, nested, compact -# SupportedStylesForModules: ~, nested, compact -Style/ClassAndModuleChildren: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedMethods, AllowedPatterns. -# AllowedMethods: ==, equal?, eql? -Style/ClassEqualityComparison: Exclude: - - 'lib/solargraph/gem_pins.rb' - - 'lib/solargraph/pin/base.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedReceivers. -Style/CollectionCompact: - Exclude: - - 'lib/solargraph/pin/constant.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/ColonMethodCall: - Exclude: - - 'spec/type_checker_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/CombinableLoops: - Exclude: - - 'lib/solargraph/pin/parameter.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/ConcatArrayLiterals: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb' + - 'spec/source/chain_spec.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions. @@ -801,211 +256,55 @@ Style/ConcatArrayLiterals: Style/ConditionalAssignment: Exclude: - 'lib/solargraph/api_map/source_to_yard.rb' - - 'lib/solargraph/parser/parser_gem/node_processors/defs_node.rb' - - 'lib/solargraph/source/chain/call.rb' # Configuration parameters: AllowedConstants. Style/Documentation: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -Style/EmptyLambdaParameter: - Exclude: - - 'spec/rbs_map/core_map_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: compact, expanded Style/EmptyMethod: Exclude: - - 'lib/solargraph/language_server/message/client/register_capability.rb' - 'spec/fixtures/formattable.rb' - 'spec/fixtures/rdoc-lib/lib/example.rb' - 'spec/fixtures/workspace-with-gemfile/lib/thing.rb' -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: trailing_conditional, ternary -Style/EmptyStringInsideInterpolation: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/pin/documenting.rb' - - 'lib/solargraph/shell.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/ExpandPathArguments: - Exclude: - - 'solargraph.gemspec' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowedVars, DefaultToNil. -Style/FetchEnvVar: - Exclude: - - 'spec/api_map/config_spec.rb' - - 'spec/spec_helper.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: left_coerce, right_coerce, single_coerce, fdiv -Style/FloatDivision: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/pin/search.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle. # SupportedStyles: always, always_true, never Style/FrozenStringLiteralComment: Enabled: false -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/GlobalStdStream: - Exclude: - - 'lib/solargraph/logging.rb' - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/shell.rb' - - 'spec/logging_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: MinBodyLength, AllowConsecutiveConditionals. Style/GuardClause: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowSplatArgument. -Style/HashConversion: - Exclude: - - 'lib/solargraph/doc_map.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowedReceivers. -# AllowedReceivers: Thread.current -Style/HashEachMethods: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/source.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, EnforcedShorthandSyntax, UseHashRocketsWithSymbolValues, PreferHashRocketsForNonAlnumEndingSymbols. -# SupportedStyles: ruby19, hash_rockets, no_mixed_keys, ruby19_no_mixed_keys -# SupportedShorthandSyntax: always, never, either, consistent, either_consistent -Style/HashSyntax: Exclude: - - 'spec/source/chain/class_variable_spec.rb' - - 'spec/source/cursor_spec.rb' - - 'spec/source/source_chainer_spec.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/IdenticalConditionalBranches: - Exclude: - - 'lib/solargraph/library.rb' - - 'lib/solargraph/type_checker.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowIfModifier. -Style/IfInsideElse: - Enabled: false + - 'lib/solargraph/source_map/clip.rb' # This cop supports safe autocorrection (--autocorrect). Style/IfUnlessModifier: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: call, braces -Style/LambdaCall: - Exclude: - - 'lib/solargraph/library.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/MapIntoArray: - Exclude: - - 'lib/solargraph/diagnostics/update_errors.rb' - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/MapToHash: - Exclude: - - 'lib/solargraph/bench.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/MapToSet: - Exclude: - - 'lib/solargraph/library.rb' - - 'spec/source_map/clip_spec.rb' - # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle. # SupportedStyles: require_parentheses, require_no_parentheses, require_no_parentheses_except_multiline Style/MethodDefParentheses: - Enabled: false + Exclude: + - 'spec/fixtures/rdoc-lib/lib/example.rb' Style/MultilineBlockChain: Exclude: - 'lib/solargraph/pin/search.rb' -# This cop supports safe autocorrection (--autocorrect). -Style/MultilineIfModifier: - Exclude: - - 'lib/solargraph/pin/callable.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/MultilineTernaryOperator: - Exclude: - - 'lib/solargraph/language_server/host.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowMethodComparison, ComparisonsThreshold. -Style/MultipleComparison: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: literals, strict -Style/MutableConstant: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: both, prefix, postfix -Style/NegatedIf: - Exclude: - - 'lib/solargraph/language_server/host/diagnoser.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/NegatedIfElseCondition: - Exclude: - - 'lib/solargraph/diagnostics/rubocop.rb' - - 'lib/solargraph/language_server/message/extended/document_gems.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/type_checker.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/NestedTernaryOperator: - Exclude: - - 'lib/solargraph/pin/conversions.rb' - - 'lib/solargraph/pin/method.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinBodyLength, AllowConsecutiveConditionals. -# SupportedStyles: skip_modifier_ifs, always -Style/Next: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/pin/signature.rb' - - 'lib/solargraph/source_map/clip.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: Strict, AllowedNumbers, AllowedPatterns. -Style/NumericLiterals: - MinDigits: 6 - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns. # SupportedStyles: predicate, comparison Style/NumericPredicate: - Enabled: false + Exclude: + - 'spec/**/*' + - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' + - 'lib/solargraph/language_server/message/extended/document_gems.rb' Style/OpenStructUse: Exclude: @@ -1016,107 +315,12 @@ Style/OpenStructUse: Style/OptionalBooleanParameter: Enabled: false -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowSafeAssignment, AllowInMultilineConditions. -Style/ParenthesesAroundCondition: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_processors/send_node.rb' - - 'lib/solargraph/type_checker.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: short, verbose -Style/PreferredHashMethods: - Exclude: - - 'lib/solargraph/language_server/message.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Methods. -Style/RedundantArgument: - Exclude: - - 'lib/solargraph/source_map/mapper.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantAssignment: - Exclude: - - 'lib/solargraph/language_server/host/dispatch.rb' - - 'lib/solargraph/workspace/config.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantBegin: - Exclude: - - 'lib/solargraph/language_server/transport/data_reader.rb' - - 'lib/solargraph/shell.rb' - - 'lib/solargraph/source/cursor.rb' - - 'lib/solargraph/source/encoding_fixes.rb' - - 'lib/solargraph/workspace.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantException: - Exclude: - - 'spec/language_server/host_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantFreeze: - Exclude: - - 'lib/solargraph/complex_type.rb' - - 'lib/solargraph/source_map/mapper.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/RedundantInterpolation: - Exclude: - - 'lib/solargraph/parser/parser_gem/node_chainer.rb' - - 'lib/solargraph/source_map/mapper.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantParentheses: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpArgument: - Exclude: - - 'lib/solargraph/api_map/index.rb' - - 'lib/solargraph/workspace/config.rb' - - 'spec/diagnostics/rubocop_helpers_spec.rb' - - 'spec/diagnostics/rubocop_spec.rb' - - 'spec/language_server/host_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantRegexpEscape: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowMultipleReturnValues. -Style/RedundantReturn: - Exclude: - - 'lib/solargraph/complex_type/type_methods.rb' - - 'lib/solargraph/parser/parser_gem/node_methods.rb' - - 'lib/solargraph/source/chain/z_super.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/RedundantSelf: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, AllowInnerSlashes. -# SupportedStyles: slashes, percent_r, mixed -Style/RegexpLiteral: - Exclude: - - 'lib/solargraph/language_server/uri_helpers.rb' - - 'lib/solargraph/workspace/config.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: implicit, explicit -Style/RescueStandardError: - Exclude: - - 'lib/solargraph/pin/base.rb' - # This cop supports unsafe autocorrection (--autocorrect-all). # Configuration parameters: ConvertCodeThatCanStartToReturnNil, AllowedMethods, MaxChainLength. # AllowedMethods: present?, blank?, presence, try, try! Style/SafeNavigation: - Enabled: false + Exclude: + - 'lib/solargraph/pin/base.rb' # Configuration parameters: Max. Style/SafeNavigationChainLength: @@ -1125,127 +329,29 @@ Style/SafeNavigationChainLength: # This cop supports unsafe autocorrection (--autocorrect-all). Style/SlicingWithRange: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: AllowModifier. -Style/SoleNestedConditional: Exclude: - - 'lib/solargraph/complex_type/unique_type.rb' - - 'lib/solargraph/pin/parameter.rb' - - 'lib/solargraph/source.rb' - - 'lib/solargraph/source/source_chainer.rb' - - 'lib/solargraph/type_checker.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/StderrPuts: - Exclude: - - 'lib/solargraph/pin/base.rb' - - 'lib/solargraph/shell.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: Mode. -Style/StringConcatenation: - Enabled: false + - 'lib/solargraph/convention/struct_definition/struct_definition_node.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline. # SupportedStyles: single_quotes, double_quotes Style/StringLiterals: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Style/SuperArguments: - Exclude: - - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/pin/signature.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinSize. -# SupportedStyles: percent, brackets -Style/SymbolArray: - Enabled: false - -# This cop supports unsafe autocorrection (--autocorrect-all). -# Configuration parameters: AllowMethodsWithArguments, AllowedMethods, AllowedPatterns, AllowComments. -# AllowedMethods: define_method -Style/SymbolProc: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, AllowSafeAssignment. -# SupportedStyles: require_parentheses, require_no_parentheses, require_parentheses_when_complex -Style/TernaryParentheses: Exclude: - - 'lib/solargraph/source_map/mapper.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, no_comma -Style/TrailingCommaInArguments: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma -Style/TrailingCommaInArrayLiteral: - Exclude: - - 'lib/solargraph/language_server/message/text_document/formatting.rb' - - 'lib/solargraph/rbs_map/core_fills.rb' + - 'spec/fixtures/rdoc-lib/rdoc-lib.gemspec' + - 'spec/source/chain_spec.rb' # This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyleForMultiline. -# SupportedStylesForMultiline: comma, consistent_comma, diff_comma, no_comma -Style/TrailingCommaInHashLiteral: +Style/SuperArguments: Exclude: - 'lib/solargraph/pin/callable.rb' - - 'lib/solargraph/pin/closure.rb' - - 'lib/solargraph/rbs_map/conversions.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, AllowedMethods. -# AllowedMethods: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym -Style/TrivialAccessors: - Exclude: - - 'lib/solargraph/language_server/message/extended/check_gem_version.rb' - -# This cop supports safe autocorrection (--autocorrect). -Style/WhileUntilModifier: - Exclude: - - 'lib/solargraph/complex_type.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle, MinSize, WordRegex. -# SupportedStyles: percent, brackets -Style/WordArray: - Enabled: false - -# This cop supports safe autocorrection (--autocorrect). -Style/YAMLFileRead: - Exclude: - - 'lib/solargraph/workspace/config.rb' - -# This cop supports unsafe autocorrection (--autocorrect-all). -Style/ZeroLengthPredicate: - Exclude: - - 'lib/solargraph/language_server/host.rb' - 'lib/solargraph/pin/method.rb' - - 'lib/solargraph/source/chain/array.rb' - - 'spec/language_server/protocol_spec.rb' - -# This cop supports safe autocorrection (--autocorrect). -# Configuration parameters: EnforcedStyle. -# SupportedStyles: long, short -YARD/CollectionType: - Exclude: - - 'lib/solargraph/range.rb' # This cop supports safe autocorrection (--autocorrect). # Configuration parameters: EnforcedStylePrototypeName. # SupportedStylesPrototypeName: before, after YARD/MismatchName: - Enabled: false + Exclude: + - 'lib/solargraph/pin/reference.rb' YARD/TagTypeSyntax: Enabled: false @@ -1254,4 +360,4 @@ YARD/TagTypeSyntax: # Configuration parameters: AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings. # URISchemes: http, https Layout/LineLength: - Max: 244 + Max: 224 diff --git a/CHANGELOG.md b/CHANGELOG.md index 315ba0c73..e07381830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ +## 0.58.2 - January 19, 2026 +- Avoid rbs pollution (#1146) +- Fix 'solargraph pin --references ClassName' private method call (#1150) +- Improve memory efficiency of Position class (#1054) +- Raise InvalidOffsetError for offsets > text (#1155) + ## 0.58.1 - January 2, 2026 -- Normalize line endings to LF (#1142) +- Normalize line endings to LF (#1142) ## 0.58.0 - January 1, 2026 - Faster constant resolution (#1083) diff --git a/Gemfile b/Gemfile index 3a5ae2b84..fafce06ec 100755 --- a/Gemfile +++ b/Gemfile @@ -1,7 +1,9 @@ -source 'https://rubygems.org' - -gemspec name: 'solargraph' - -# Local gemfile for development tools, etc. -local_gemfile = File.expand_path(".Gemfile", __dir__) -instance_eval File.read local_gemfile if File.exist? local_gemfile +# frozen_string_literal: true + +source 'https://rubygems.org' + +gemspec name: 'solargraph' + +# Local gemfile for development tools, etc. +local_gemfile = File.expand_path('.Gemfile', __dir__) +instance_eval File.read local_gemfile if File.exist? local_gemfile diff --git a/Rakefile b/Rakefile index f27abfeb6..398957b1a 100755 --- a/Rakefile +++ b/Rakefile @@ -1,138 +1,140 @@ -require 'rake' -require 'bundler/gem_tasks' -require 'fileutils' -require 'open3' - -desc "Open a Pry session preloaded with this library" -task :console do - sh "pry -I lib -r solargraph.rb" -end - -desc "Run the type checker" -task typecheck: [:typecheck_strong] - -desc "Run the type checker at typed level - return code issues provable without annotations being correct" -task :typecheck_typed do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level typed" -end - -desc "Run the type checker at strict level - report issues using type annotations" -task :typecheck_strict do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strict" -end - -desc "Run the type checker at strong level - enforce that type annotations exist" -task :typecheck_strong do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong" -end - -desc "Run the type checker at alpha level - run high-false-alarm checks" -task :typecheck_alpha do - sh "SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level alpha" -end - -desc "Run RSpec tests, starting with the ones that failed last time" -task spec: %i[spec_failed undercover_no_fail full_spec] do - undercover -end - -desc "Run all RSpec tests" -task :full_spec do - warn 'starting spec' - sh 'TEST_COVERAGE_COMMAND_NAME=full-new bundle exec rspec' # --profile' - warn 'ending spec' - # move coverage/full-new to coverage/full on success so that we - # always have the last successful run's 'coverage info - FileUtils.rm_rf('coverage/full') - FileUtils.mv('coverage/full-new', 'coverage/full') -end - -# @sg-ignore #undercover return type could not be inferred -# @return [Process::Status] -def undercover - simplecov_collate - cmd = 'bundle exec undercover ' \ - '--simplecov coverage/combined/coverage.json ' \ - '--exclude-files "Rakefile,spec/*,spec/**/*,lib/solargraph/version.rb" ' \ - '--compare origin/master' - output, status = Bundler.with_unbundled_env do - Open3.capture2e(cmd) - end - puts output - $stdout.flush - status -rescue StandardError => e - warn "hit error: #{e.message}" - # @sg-ignore Need to add nil check here - warn "Backtrace:\n#{e.backtrace.join("\n")}" - warn "output: #{output}" - puts "Flushing" - $stdout.flush - raise -end - -desc "Check PR coverage" -task :undercover do - raise "Undercover failed" unless undercover.success? -end - -desc "Branch-focused fast-feedback quality/spec/coverage checks" -task test: %i[overcommit spec typecheck] do - # do these in order - Rake::Task['typecheck_strict'].invoke - Rake::Task['typecheck_strong'].invoke - Rake::Task['typecheck_alpha'].invoke -end - -desc "Re-run failed specs. Add --fail-fast in your .rspec-local file if desired." -task :spec_failed do - # allow user to check out any persistent failures while looking for - # more in the whole test suite - sh 'TEST_COVERAGE_COMMAND_NAME=next-failure bundle exec rspec --only-failures || true' -end - -desc "Run undercover and show output without failing the task if it fails" -task :undercover_no_fail do - undercover -rescue StandardError - puts "Undercover failed, but continuing with other tasks." -end - -# @return [void] -def simplecov_collate - require 'simplecov' - require 'simplecov-lcov' - require 'undercover/simplecov_formatter' - - SimpleCov.collate(Dir["coverage/{next-failure,full,ad-hoc}/.resultset.json"]) do - cname = 'combined' - command_name cname - new_dir = File.join('coverage', cname) - coverage_dir new_dir - - formatter \ - SimpleCov::Formatter::MultiFormatter - .new([ - SimpleCov::Formatter::HTMLFormatter, - SimpleCov::Formatter::Undercover, - SimpleCov::Formatter::LcovFormatter - ]) - SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true - end - puts "Simplecov collated results into coverage/combined/.resultset.json" -rescue StandardError => e - puts "Simplecov collate failed: #{e.message}" -ensure - $stdout.flush -end - -desc 'Add incremental coverage for rapid iteration with undercover' -task :simplecov_collate do - simplecov_collate -end - -desc "Show quality checks on this development branch so far, including any staged files" -task :overcommit do - # OVERCOMMIT_DEBUG=1 will show more detail - sh 'SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master' -end +# frozen_string_literal: true + +require 'rake' +require 'bundler/gem_tasks' +require 'fileutils' +require 'open3' + +desc 'Open a Pry session preloaded with this library' +task :console do + sh 'pry -I lib -r solargraph.rb' +end + +desc 'Run the type checker' +task typecheck: [:typecheck_strong] + +desc 'Run the type checker at typed level - return code issues provable without annotations being correct' +task :typecheck_typed do + sh 'SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level typed' +end + +desc 'Run the type checker at strict level - report issues using type annotations' +task :typecheck_strict do + sh 'SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strict' +end + +desc 'Run the type checker at strong level - enforce that type annotations exist' +task :typecheck_strong do + sh 'SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level strong' +end + +desc 'Run the type checker at alpha level - run high-false-alarm checks' +task :typecheck_alpha do + sh 'SOLARGRAPH_ASSERTS=on bundle exec solargraph typecheck --level alpha' +end + +desc 'Run RSpec tests, starting with the ones that failed last time' +task spec: %i[spec_failed undercover_no_fail full_spec] do + undercover +end + +desc 'Run all RSpec tests' +task :full_spec do + warn 'starting spec' + sh 'TEST_COVERAGE_COMMAND_NAME=full-new bundle exec rspec' # --profile' + warn 'ending spec' + # move coverage/full-new to coverage/full on success so that we + # always have the last successful run's 'coverage info + FileUtils.rm_rf('coverage/full') + FileUtils.mv('coverage/full-new', 'coverage/full') +end + +# @sg-ignore #undercover return type could not be inferred +# @return [Process::Status] +def undercover + simplecov_collate + cmd = 'bundle exec undercover ' \ + '--simplecov coverage/combined/coverage.json ' \ + '--exclude-files "Rakefile,spec/*,spec/**/*,lib/solargraph/version.rb" ' \ + '--compare origin/master' + output, status = Bundler.with_unbundled_env do + Open3.capture2e(cmd) + end + puts output + $stdout.flush + status +rescue StandardError => e + warn "hit error: #{e.message}" + # @sg-ignore Need to add nil check here + warn "Backtrace:\n#{e.backtrace.join("\n")}" + warn "output: #{output}" + puts 'Flushing' + $stdout.flush + raise +end + +desc 'Check PR coverage' +task :undercover do + raise 'Undercover failed' unless undercover.success? +end + +desc 'Branch-focused fast-feedback quality/spec/coverage checks' +task test: %i[overcommit spec typecheck] do + # do these in order + Rake::Task['typecheck_strict'].invoke + Rake::Task['typecheck_strong'].invoke + Rake::Task['typecheck_alpha'].invoke +end + +desc 'Re-run failed specs. Add --fail-fast in your .rspec-local file if desired.' +task :spec_failed do + # allow user to check out any persistent failures while looking for + # more in the whole test suite + sh 'TEST_COVERAGE_COMMAND_NAME=next-failure bundle exec rspec --only-failures || true' +end + +desc 'Run undercover and show output without failing the task if it fails' +task :undercover_no_fail do + undercover +rescue StandardError + puts 'Undercover failed, but continuing with other tasks.' +end + +# @return [void] +def simplecov_collate + require 'simplecov' + require 'simplecov-lcov' + require 'undercover/simplecov_formatter' + + SimpleCov.collate(Dir['coverage/{next-failure,full,ad-hoc}/.resultset.json']) do + cname = 'combined' + command_name cname + new_dir = File.join('coverage', cname) + coverage_dir new_dir + + formatter \ + SimpleCov::Formatter::MultiFormatter + .new([ + SimpleCov::Formatter::HTMLFormatter, + SimpleCov::Formatter::Undercover, + SimpleCov::Formatter::LcovFormatter + ]) + SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true + end + puts 'Simplecov collated results into coverage/combined/.resultset.json' +rescue StandardError => e + puts "Simplecov collate failed: #{e.message}" +ensure + $stdout.flush +end + +desc 'Add incremental coverage for rapid iteration with undercover' +task :simplecov_collate do + simplecov_collate +end + +desc 'Show quality checks on this development branch so far, including any staged files' +task :overcommit do + # OVERCOMMIT_DEBUG=1 will show more detail + sh 'SOLARGRAPH_ASSERTS=on bundle exec overcommit --run --diff origin/master' +end diff --git a/bin/solargraph b/bin/solargraph index 248dc42fd..cd5341820 100755 --- a/bin/solargraph +++ b/bin/solargraph @@ -1,7 +1,8 @@ #!/usr/bin/env ruby +# frozen_string_literal: true # turn off warning diagnostics from Ruby -$VERBOSE=nil +$VERBOSE = nil require 'solargraph' diff --git a/lib/solargraph.rb b/lib/solargraph.rb index 27a79e4ad..544a4e2e2 100755 --- a/lib/solargraph.rb +++ b/lib/solargraph.rb @@ -71,7 +71,7 @@ def self.asserts_on? # @param msg [String, nil] An optional message to log # @param block [Proc] A block that returns a message to log # @return [void] - def self.assert_or_log(type, msg = nil, &block) + def self.assert_or_log type, msg = nil, &block if asserts_on? # @type [String, nil] msg ||= block.call @@ -113,10 +113,10 @@ def self.logger # @return [generic] def self.with_clean_env &block meth = if Bundler.respond_to?(:with_original_env) - :with_original_env - else - :with_clean_env - end + :with_original_env + else + :with_clean_env + end Bundler.send meth, &block end end diff --git a/lib/solargraph/api_map.rb b/lib/solargraph/api_map.rb index 904edff8e..88e82a692 100755 --- a/lib/solargraph/api_map.rb +++ b/lib/solargraph/api_map.rb @@ -51,15 +51,15 @@ def self.reset_core out: nil # # @param other [Object] - def eql?(other) + def eql? other self.class == other.class && # @sg-ignore flow sensitive typing needs to handle self.class == other.class equality_fields == other.equality_fields end # @param other [Object] - def ==(other) - self.eql?(other) + def == other + eql?(other) end # @return [Integer] @@ -127,13 +127,6 @@ def catalog bench self end - # @todo need to model type def statement in chains as a symbol so - # that this overload of 'protected' will typecheck @sg-ignore - # @sg-ignore - protected def equality_fields - [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires, @missing_docs, @loose_unions] - end - # @return [DocMap] def doc_map @doc_map ||= DocMap.new([], Workspace.new('.')) @@ -212,7 +205,7 @@ def cache_all_for_doc_map! out: $stderr, rebuild: false # @param rebuild [Boolean] # @param out [StringIO, IO, nil] # @return [void] - def cache_gem(gemspec, rebuild: false, out: nil) + def cache_gem gemspec, rebuild: false, out: nil doc_map.cache(gemspec, rebuild: rebuild, out: out) end @@ -317,19 +310,19 @@ def resolve name, *gates # # @param pin [Pin::Reference] # @return [String, nil] - def dereference(pin) + def dereference pin store.constants.dereference(pin) end # @param fqns [String] # @return [Array] - def get_extends(fqns) + def get_extends fqns store.get_extends(fqns) end # @param fqns [String] # @return [Array] - def get_includes(fqns) + def get_includes fqns store.get_includes(fqns) end @@ -341,7 +334,7 @@ def get_includes(fqns) # @return [Array] def get_instance_variable_pins namespace, scope = :instance result = [] - used = [namespace] + [namespace] result.concat store.get_instance_variables(namespace, scope) sc_fqns = namespace while (sc = store.get_superclass(sc_fqns)) @@ -364,12 +357,12 @@ def get_instance_variable_pins namespace, scope = :instance # @param location [Location] # # @return [Pin::BaseVariable, nil] - def var_at_location(candidates, name, closure, location) - with_correct_name = candidates.select { |pin| pin.name == name} + def var_at_location candidates, name, closure, location + with_correct_name = candidates.select { |pin| pin.name == name } vars_at_location = with_correct_name.reject do |pin| # visible_at? excludes the starting position, but we want to # include it for this purpose - (!pin.visible_at?(closure, location) && !pin.starts_at?(location)) + !pin.visible_at?(closure, location) && !pin.starts_at?(location) end vars_at_location.inject(&:combine_with) @@ -446,7 +439,7 @@ def get_methods rooted_tag, scope: :instance, visibility: [:public], deep: true comments: init_pin.comments, closure: init_pin.closure, source: init_pin.source, - type_location: init_pin.type_location, + type_location: init_pin.type_location ) new_pin.parameters = init_pin.parameters.map do |init_param| param = init_param.clone @@ -505,7 +498,7 @@ def get_complex_type_methods complex_type, context = '', internal = false result = Set.new complex_type.each do |type| if type.duck_type? - result.add Pin::DuckMethod.new(name: type.to_s[1..-1], source: :api_map) + result.add Pin::DuckMethod.new(name: type.to_s[1..], source: :api_map) result.merge get_methods('Object') else unless type.nil? || type.name == 'void' @@ -536,7 +529,8 @@ def get_complex_type_methods complex_type, context = '', internal = false # @param preserve_generics [Boolean] True to preserve any # unresolved generic parameters, false to erase them # @return [Array] - def get_method_stack rooted_tag, name, scope: :instance, visibility: [:private, :protected, :public], preserve_generics: false + def get_method_stack rooted_tag, name, scope: :instance, visibility: %i[private protected public], + preserve_generics: false rooted_type = ComplexType.parse(rooted_tag) fqns = rooted_type.namespace namespace_pin = store.get_path_pins(fqns).first @@ -661,7 +655,7 @@ def bundled? filename # @param sup [String] The superclass # @param sub [String] The subclass # @return [Boolean] - def super_and_sub?(sup, sub) + def super_and_sub? sup, sub sup = ComplexType.try_parse(sup) sub = ComplexType.try_parse(sub) # @todo If two literals are different values of the same type, it would @@ -693,14 +687,14 @@ def super_and_sub?(sup, sub) # @param module_ns [String] The module namespace (no type parameters) # # @return [Boolean] - def type_include?(host_ns, module_ns) + def type_include? host_ns, module_ns store.get_includes(host_ns).map { |inc_tag| inc_tag.type.name }.include?(module_ns) end # @param pins [Enumerable] # @param visibility [Enumerable] # @return [Array] - def resolve_method_aliases pins, visibility = [:public, :private, :protected] + def resolve_method_aliases pins, visibility = %i[public private protected] with_resolved_aliases = pins.map do |pin| next pin unless pin.is_a?(Pin::MethodAlias) resolved = resolve_method_alias(pin) @@ -730,7 +724,7 @@ def workspace # @param skip [Set] # @param no_core [Boolean] Skip core classes if true # @return [Array] - def inner_get_methods_from_reference(fq_reference_tag, namespace_pin, type, scope, visibility, deep, skip, no_core) + def inner_get_methods_from_reference fq_reference_tag, namespace_pin, type, scope, visibility, deep, skip, no_core logger.debug { "ApiMap#add_methods_from_reference(type=#{type}) starting" } # Ensure the types returned by the methods in the referenced @@ -786,7 +780,7 @@ def store def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false rooted_type = ComplexType.parse(rooted_tag).force_rooted fqns = rooted_type.namespace - fqns_generic_params = rooted_type.all_params + rooted_type.all_params namespace_pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first return [] if no_core && fqns =~ /^(Object|BasicObject|Class|Module)$/ reqstr = "#{fqns}|#{scope}|#{visibility.sort}|#{deep}" @@ -797,7 +791,9 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false # ensure we start out with any immediate methods in this # namespace so we roughly match the same ordering of get_methods # and obey the 'deep' instruction - direct_convention_methods, convention_methods_by_reference = environ.pins.partition { |p| p.namespace == rooted_tag } + direct_convention_methods, convention_methods_by_reference = environ.pins.partition do |p| + p.namespace == rooted_tag + end result.concat direct_convention_methods if deep && scope == :instance @@ -809,8 +805,10 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false # Store#get_methods doesn't know about full tags, just # namespaces; resolving the generics in the method pins is this # class' responsibility - methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort{ |a, b| a.name <=> b.name } - logger.info { "ApiMap#inner_get_methods(rooted_tag=#{rooted_tag.inspect}, scope=#{scope.inspect}, visibility=#{visibility.inspect}, deep=#{deep.inspect}, skip=#{skip.inspect}, fqns=#{fqns}) - added from store: #{methods}" } + methods = store.get_methods(fqns, scope: scope, visibility: visibility).sort { |a, b| a.name <=> b.name } + logger.info do + "ApiMap#inner_get_methods(rooted_tag=#{rooted_tag.inspect}, scope=#{scope.inspect}, visibility=#{visibility.inspect}, deep=#{deep.inspect}, skip=#{skip.inspect}, fqns=#{fqns}) - added from store: #{methods}" + end result.concat methods if deep result.concat convention_methods_by_reference @@ -819,7 +817,8 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false store.get_includes(fqns).reverse.each do |ref| in_tag = dereference(ref) # @sg-ignore Need to add nil check here - result.concat inner_get_methods_from_reference(in_tag, namespace_pin, rooted_type, scope, visibility, deep, skip, true) + result.concat inner_get_methods_from_reference(in_tag, namespace_pin, rooted_type, scope, visibility, deep, + skip, true) end rooted_sc_tag = qualify_superclass(rooted_tag) unless rooted_sc_tag.nil? @@ -827,7 +826,9 @@ def inner_get_methods rooted_tag, scope, visibility, deep, skip, no_core = false visibility, true, skip, no_core) end else - logger.info { "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" } + logger.info do + "ApiMap#inner_get_methods(#{fqns}, #{scope}, #{visibility}, #{deep}, #{skip}) - looking for get_extends() from #{fqns}" + end store.get_extends(fqns).reverse.each do |em| fqem = dereference(em) result.concat inner_get_methods(fqem, :instance, visibility, deep, skip, true) unless fqem.nil? @@ -863,7 +864,7 @@ def path_macros def get_namespace_type fqns return nil if fqns.nil? # @type [Pin::Namespace, nil] - pin = store.get_path_pins(fqns).select{|p| p.is_a?(Pin::Namespace)}.first + pin = store.get_path_pins(fqns).select { |p| p.is_a?(Pin::Namespace) }.first return nil if pin.nil? pin.type end @@ -889,7 +890,7 @@ def prefer_non_nil_variables pins # @param alias_pin [Pin::MethodAlias] # @return [Pin::Method, nil] - def resolve_method_alias(alias_pin) + def resolve_method_alias alias_pin ancestors = store.get_ancestors(alias_pin.full_context.reduce_class_type.tag) # @type [Pin::Method, nil] original = nil @@ -920,7 +921,9 @@ def resolve_method_alias(alias_pin) end if original.nil? # :nocov: - Solargraph.assert_or_log(:alias_target_missing) { "Rejecting alias - target is missing while looking for #{alias_pin.full_context.tag} #{alias_pin.original} in #{alias_pin.scope} scope = #{alias_pin.inspect}" } + Solargraph.assert_or_log(:alias_target_missing) do + "Rejecting alias - target is missing while looking for #{alias_pin.full_context.tag} #{alias_pin.original} in #{alias_pin.scope} scope = #{alias_pin.inspect}" + end return nil # :nocov: end @@ -933,7 +936,7 @@ def resolve_method_alias(alias_pin) # @param alias_pin [Pin::MethodAlias] The alias pin to resolve # @param original [Pin::Method] The original method pin that was already found # @return [Pin::Method] The resolved method pin - def create_resolved_alias_pin(alias_pin, original) + def create_resolved_alias_pin alias_pin, original # Build the resolved method pin directly (same logic as resolve_method_alias but without lookup) args = { location: alias_pin.location, @@ -949,7 +952,7 @@ def create_resolved_alias_pin(alias_pin, original) return_type: original.return_type, source: :resolve_method_alias } - resolved_pin = Pin::Method.new **args + resolved_pin = Pin::Method.new(**args) # Clone signatures and parameters resolved_pin.signatures.each do |sig| @@ -987,7 +990,7 @@ def should_erase_generics_when_done? namespace_pin, rooted_type end # @param namespace_pin [Pin::Namespace, Pin::Constant] - def has_generics?(namespace_pin) + def has_generics? namespace_pin namespace_pin.is_a?(Pin::Namespace) && !namespace_pin.generics.empty? end @@ -996,5 +999,15 @@ def has_generics?(namespace_pin) def can_resolve_generics? namespace_pin, rooted_type has_generics?(namespace_pin) && !rooted_type.all_params.empty? end + + protected + + # @todo need to model type def statement in chains as a symbol so + # that this overload of 'protected' will typecheck @sg-ignore + # @sg-ignore + def equality_fields + [self.class, @source_map_hash, conventions_environ, @doc_map, @unresolved_requires, @missing_docs, + @loose_unions] + end end end diff --git a/lib/solargraph/api_map/index.rb b/lib/solargraph/api_map/index.rb index 48cf05706..df3e6984e 100644 --- a/lib/solargraph/api_map/index.rb +++ b/lib/solargraph/api_map/index.rb @@ -118,15 +118,15 @@ def catalog new_pins # @param k [String] # @param v [Set] set.classify(&:class) - .map { |k, v| pin_class_hash[k].concat v.to_a } + .map { |k, v| pin_class_hash[k].concat v.to_a } # @param k [String] # @param v [Set] set.classify(&:namespace) - .map { |k, v| namespace_hash[k].concat v.to_a } + .map { |k, v| namespace_hash[k].concat v.to_a } # @param k [String] # @param v [Set] set.classify(&:path) - .map { |k, v| path_pin_hash[k].concat v.to_a } + .map { |k, v| path_pin_hash[k].concat v.to_a } @namespaces = path_pin_hash.keys.compact.to_set map_references Pin::Reference::Include, include_references map_references Pin::Reference::Prepend, prepend_references @@ -157,20 +157,14 @@ def map_overrides pins = path_pin_hash[ovr.name] logger.debug { "ApiMap::Index#map_overrides: pins for path=#{ovr.name}: #{pins}" } pins.each do |pin| - new_pin = if pin.path.end_with?('#initialize') - path_pin_hash[pin.path.sub(/#initialize/, '.new')].first - end + new_pin = (path_pin_hash[pin.path.sub('#initialize', '.new')].first if pin.path.end_with?('#initialize')) (ovr.tags.map(&:tag_name) + ovr.delete).uniq.each do |tag| # @sg-ignore Wrong argument type for # YARD::Docstring#delete_tags: name expected String, # received String, Symbol - delete_tags is ok with a # _ToS, but we should fix anyway pin.docstring.delete_tags tag - # @sg-ignore Wrong argument type for - # YARD::Docstring#delete_tags: name expected String, - # received String, Symbol - delete_tags is ok with a - # _ToS, but we should fix anyway - new_pin.docstring.delete_tags tag if new_pin + new_pin&.docstring&.delete_tags tag end ovr.tags.each do |tag| pin.docstring.add_tag(tag) diff --git a/lib/solargraph/api_map/source_to_yard.rb b/lib/solargraph/api_map/source_to_yard.rb index 05010c636..a121a348b 100644 --- a/lib/solargraph/api_map/source_to_yard.rb +++ b/lib/solargraph/api_map/source_to_yard.rb @@ -3,7 +3,6 @@ module Solargraph class ApiMap module SourceToYard - # Get the YARD CodeObject at the specified path. # # @sg-ignore Declared return type generic, nil does not match @@ -15,7 +14,7 @@ module SourceToYard # @return [generic, nil] def code_object_at path, klass = YARD::CodeObjects::Base obj = code_object_map[path] - obj if obj&.is_a?(klass) + obj if obj.is_a?(klass) end # @return [Array] @@ -36,20 +35,20 @@ def rake_yard store end if pin.type == :class # @param obj [YARD::CodeObjects::RootObject] - code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) { |obj| + code_object_map[pin.path] ||= YARD::CodeObjects::ClassObject.new(root_code_object, pin.path) do |obj| # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) - } + end else # @param obj [YARD::CodeObjects::RootObject] - code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) { |obj| + code_object_map[pin.path] ||= YARD::CodeObjects::ModuleObject.new(root_code_object, pin.path) do |obj| # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file(pin.location.filename, pin.location.range.start.line, !pin.comments.empty?) - } + end end code_object_map[pin.path].docstring = pin.docstring store.get_includes(pin.path).each do |ref| @@ -75,12 +74,14 @@ def rake_yard store # @sg-ignore Need to add nil check here # @param obj [YARD::CodeObjects::RootObject] - code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new(code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope) { |obj| + code_object_map[pin.path] ||= YARD::CodeObjects::MethodObject.new( + code_object_at(pin.namespace, YARD::CodeObjects::NamespaceObject), pin.name, pin.scope + ) do |obj| # @sg-ignore flow sensitive typing needs to handle attrs next if pin.location.nil? || pin.location.filename.nil? # @sg-ignore flow sensitive typing needs to handle attrs obj.add_file pin.location.filename, pin.location.range.start.line - } + end method_object = code_object_at(pin.path, YARD::CodeObjects::MethodObject) # @sg-ignore Need to add nil check here method_object.docstring = pin.docstring diff --git a/lib/solargraph/api_map/store.rb b/lib/solargraph/api_map/store.rb index bb371d173..29b58bb1a 100644 --- a/lib/solargraph/api_map/store.rb +++ b/lib/solargraph/api_map/store.rb @@ -32,16 +32,16 @@ def update *pinsets # @todo Fix this map @fqns_pins_map = nil - return catalog(pinsets) if changed == 0 + return catalog(pinsets) if changed.zero? # @sg-ignore Need to add nil check here pinsets[changed..].each_with_index do |pins, idx| @pinsets[changed + idx] = pins @indexes[changed + idx] = if pins.empty? - @indexes[changed + idx - 1] - else - @indexes[changed + idx - 1].merge(pins) - end + @indexes[changed + idx - 1] + else + @indexes[changed + idx - 1].merge(pins) + end end constants.clear cached_qualify_superclass.clear @@ -60,10 +60,10 @@ def inspect # @param visibility [Array] # @return [Enumerable] def get_constants fqns, visibility = [:public] - namespace_children(fqns).select { |pin| + namespace_children(fqns).select do |pin| # @sg-ignore flow sensitive typing not smart enough to handle this case !pin.name.empty? && (pin.is_a?(Pin::Namespace) || pin.is_a?(Pin::Constant)) && visibility.include?(pin.visibility) - } + end end # @param fqns [String] @@ -77,8 +77,10 @@ def get_methods fqns, scope: :instance, visibility: [:public] GemPins.combine_method_pins_by_path(all_pins) end - BOOLEAN_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Boolean', closure: Pin::ROOT_PIN, source: :solargraph) - OBJECT_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Object', closure: Pin::ROOT_PIN, source: :solargraph) + BOOLEAN_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Boolean', closure: Pin::ROOT_PIN, + source: :solargraph) + OBJECT_SUPERCLASS_PIN = Pin::Reference::Superclass.new(name: 'Object', closure: Pin::ROOT_PIN, + source: :solargraph) # @param fqns [String, nil] # @return [Pin::Reference::Superclass, nil] @@ -129,17 +131,17 @@ def get_path_pins path # @param fqns [String, nil] # @param scope [Symbol] :class or :instance # @return [Enumerable] - def get_instance_variables(fqns, scope = :instance) - all_instance_variables.select { |pin| + def get_instance_variables fqns, scope = :instance + all_instance_variables.select do |pin| pin.binder.namespace == fqns && pin.binder.scope == scope - } + end end # @param fqns [String] # # @return [Enumerable] - def get_class_variables(fqns) - namespace_children(fqns).select { |pin| pin.is_a?(Pin::ClassVariable)} + def get_class_variables fqns + namespace_children(fqns).select { |pin| pin.is_a?(Pin::ClassVariable) } end # @return [Enumerable] @@ -149,7 +151,7 @@ def get_symbols # @param fqns [String] # @return [Boolean] - def namespace_exists?(fqns) + def namespace_exists? fqns fqns_pins(fqns).any? end @@ -165,7 +167,7 @@ def method_pins # @param fqns [String] # @return [Array] - def domains(fqns) + def domains fqns result = [] fqns_pins(fqns).each do |nspin| result.concat nspin.domains @@ -178,7 +180,7 @@ def named_macros @named_macros ||= begin result = {} pins.each do |pin| - pin.macros.select{|m| m.tag.tag_name == 'macro' && !m.tag.text.empty? }.each do |macro| + pin.macros.select { |m| m.tag.tag_name == 'macro' && !m.tag.text.empty? }.each do |macro| next if macro.tag.name.nil? || macro.tag.name.empty? result[macro.tag.name] = macro end @@ -217,7 +219,7 @@ def fqns_pins fqns # Get all ancestors (superclasses, includes, prepends, extends) for a namespace # @param fqns [String] The fully qualified namespace # @return [Array] Array of ancestor namespaces including the original - def get_ancestors(fqns) + def get_ancestors fqns return [] if fqns.nil? || fqns.empty? ancestors = [fqns] @@ -260,7 +262,7 @@ def get_ancestors(fqns) # @param fqns [String] # # @return [Array] - def get_ancestor_references(fqns) + def get_ancestor_references fqns (get_prepends(fqns) + get_includes(fqns) + [get_superclass(fqns)]).compact end @@ -350,7 +352,7 @@ def all_instance_variables # @param fqns [String] # @return [Pin::Reference::Superclass, nil] - def try_special_superclasses(fqns) + def try_special_superclasses fqns return OBJECT_SUPERCLASS_PIN if fqns == 'Boolean' return OBJECT_SUPERCLASS_PIN if !%w[BasicObject Object].include?(fqns) && namespace_exists?(fqns) diff --git a/lib/solargraph/bench.rb b/lib/solargraph/bench.rb index e6180c933..dda2bbc88 100644 --- a/lib/solargraph/bench.rb +++ b/lib/solargraph/bench.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true - module Solargraph # A container of source maps and workspace data to be cataloged in an ApiMap. # @@ -30,11 +29,11 @@ def initialize source_maps: [], workspace: Workspace.new, live_map: nil, externa .to_set end + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars # @return [Hash{String => SourceMap}] def source_map_hash # @todo Work around #to_h bug in current Ruby head (3.5) with #map#to_h - @source_map_hash ||= source_maps.map { |s| [s.filename, s] } - .to_h + @source_map_hash ||= source_maps.to_h { |s| [s.filename, s] } end # @return [Set] diff --git a/lib/solargraph/complex_type.rb b/lib/solargraph/complex_type.rb index c67f9c2a4..c2dcf469b 100644 --- a/lib/solargraph/complex_type.rb +++ b/lib/solargraph/complex_type.rb @@ -4,7 +4,7 @@ module Solargraph # A container for type data based on YARD type tags. # class ComplexType - GENERIC_TAG_NAME = 'generic'.freeze + GENERIC_TAG_NAME = 'generic' # @!parse # include TypeMethods include Equality @@ -19,7 +19,7 @@ def initialize types = [UniqueType::UNDEFINED] # @type [Array] items = types.flat_map(&:items).uniq(&:to_s) if items.any? { |i| i.name == 'false' } && items.any? { |i| i.name == 'true' } - items.delete_if { |i| i.name == 'false' || i.name == 'true' } + items.delete_if { |i| %w[false true].include?(i.name) } items.unshift(UniqueType::BOOLEAN) end # @type [Array] @@ -29,17 +29,14 @@ def initialize types = [UniqueType::UNDEFINED] @items = items end - protected def equality_fields - [self.class, items] - end - # @param api_map [ApiMap] - # @param context [String] + # @param gates [Array] + # # @return [ComplexType] def qualify api_map, *gates red = reduce_object types = red.items.map do |t| - next t if ['nil', 'void', 'undefined'].include?(t.name) + next t if %w[nil void undefined].include?(t.name) next t if ['::Boolean'].include?(t.rooted_name) t.qualify api_map, *gates end @@ -53,7 +50,10 @@ def qualify api_map, *gates def resolve_generics_from_context generics_to_resolve, context_type, resolved_generic_values: {} return self unless generic? - ComplexType.new(@items.map { |i| i.resolve_generics_from_context(generics_to_resolve, context_type, resolved_generic_values: resolved_generic_values) }) + ComplexType.new(@items.map do |i| + i.resolve_generics_from_context(generics_to_resolve, context_type, + resolved_generic_values: resolved_generic_values) + end) end # @return [UniqueType] @@ -84,14 +84,14 @@ def self_to_type dst # @sg-ignore Declared return type # ::Array<::Solargraph::ComplexType::UniqueType> does not match # inferred type ::Array<::Proc> for Solargraph::ComplexType#map - def map(&block) + def map &block @items.map(&block) end # @yieldparam [UniqueType] # @return [Enumerable] def each &block - @items.each &block + @items.each(&block) end # @yieldparam [UniqueType] @@ -102,17 +102,17 @@ def each_unique_type &block return enum_for(__method__) unless block_given? @items.each do |item| - item.each_unique_type &block + item.each_unique_type(&block) end end # @param new_name [String, nil] # @param make_rooted [Boolean, nil] # @param new_key_types [Array, nil] - # @param rooted [Boolean, nil] + # @param make_rooted [Boolean, nil] # @param new_subtypes [Array, nil] # @return [self] - def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil) + def recreate new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil ComplexType.new(map do |ut| ut.recreate(new_name: new_name, make_rooted: make_rooted, @@ -133,13 +133,13 @@ def to_a # @param index [Integer] # @return [UniqueType] - def [](index) + def [] index @items[index] end # @return [Array] def select &block - @items.select &block + @items.select(&block) end # @return [String] @@ -154,7 +154,9 @@ def namespaces end # @param name [Symbol] + # # @return [Object, nil] + # @param [Array] args def method_missing name, *args, &block return if @items.first.nil? return @items.first.send(name, *args, &block) if respond_to_missing?(name) @@ -163,7 +165,7 @@ def method_missing name, *args, &block # @param name [Symbol] # @param include_private [Boolean] - def respond_to_missing?(name, include_private = false) + def respond_to_missing? name, include_private = false TypeMethods.public_instance_methods.include?(name) || super end @@ -198,24 +200,29 @@ def desc # @param api_map [ApiMap] # @param expected [ComplexType, ComplexType::UniqueType] # @param situation [:method_call, :return_type, :assignment] - # @param allow_subtype_skew [Boolean] if false, check if any - # subtypes of the expected type match the inferred type - # @param allow_reverse_match [Boolean] if true, check if any subtypes - # of the expected type match the inferred type - # @param allow_empty_params [Boolean] if true, allow a general - # inferred type without parameters to conform to a more specific - # expected type - # @param allow_any_match [Boolean] if true, any unique type - # matched in the inferred qualifies as a match - # @param allow_undefined [Boolean] if true, treat undefined as a - # wildcard that matches anything # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic, :allow_unmatched_interface>] + # + # allow_subtype_skew: if not provided, check if any subtypes of + # the expected type match the inferred type + # + # allow_reverse_match: check if any subtypes + # of the expected type match the inferred type + # + # allow_empty_params: allow a general inferred type without + # parameters to conform to a more specific expected type + # + # allow_any_match: any unique type matched in the inferred + # qualifies as a match + # + # allow_undefined: treat undefined as a wildcard that matches + # anything + # # @param variance [:invariant, :covariant, :contravariant] # @return [Boolean] - def conforms_to?(api_map, expected, + def conforms_to? api_map, expected, situation, rules = [], - variance: erased_variance(situation)) + variance: erased_variance(situation) expected = expected.downcast_to_literal_if_possible inferred = downcast_to_literal_if_possible @@ -256,14 +263,14 @@ def rooted_tags # @yieldparam [UniqueType] def all? &block - @items.all? &block + @items.all?(&block) end # @yieldparam [UniqueType] # @yieldreturn [Boolean] # @return [Boolean] def any? &block - @items.compact.any? &block + @items.compact.any?(&block) end def selfy? @@ -283,8 +290,10 @@ def simplify_literals # @yieldparam t [UniqueType] # @yieldreturn [UniqueType] # @return [ComplexType] - def transform(new_name = nil, &transform_type) - raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" if new_name&.start_with?('::') + def transform new_name = nil, &transform_type + if new_name&.start_with?('::') + raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" + end ComplexType.new(map { |ut| ut.transform(new_name, &transform_type) }) end @@ -322,7 +331,7 @@ def all_params # @return [ComplexType] def reduce_class_type new_items = items.flat_map do |type| - next type unless ['Module', 'Class'].include?(type.name) + next type unless %w[Module Class].include?(type.name) next type if type.all_params.empty? type.all_params @@ -337,7 +346,7 @@ def all_rooted? end # @param other [ComplexType, UniqueType] - def erased_version_of?(other) + def erased_version_of? other return false if items.length != 1 || other.items.length != 1 @items.first.erased_version_of?(other.items.first) @@ -351,10 +360,6 @@ def rooted? attr_reader :items - def rooted? - @items.all?(&:rooted?) - end - # @param exclude_types [ComplexType, nil] # @param api_map [ApiMap] # @return [ComplexType, self] @@ -391,6 +396,10 @@ def intersect_with intersection_type, api_map protected + def equality_fields + [self.class, items] + end + # @return [ComplexType] def reduce_object new_items = items.flat_map do |ut| @@ -410,13 +419,11 @@ class << self # @example # ComplexType.parse 'String', 'Foo', 'nil' #=> [String, Foo, nil] # - # @note - # The `partial` parameter is used to indicate that the method is - # receiving a string that will be used inside another ComplexType. - # It returns arrays of ComplexTypes instead of a single cohesive one. - # Consumers should not need to use this parameter; it should only be - # used internally. - # + # @param partial [Boolean] if true, method is receiving a string + # that will be used inside another ComplexType. It returns + # arrays of ComplexTypes instead of a single cohesive one. + # Consumers should not need to use this parameter; it should + # only be used internally. # @param strings [Array] The type definitions to parse # @return [ComplexType] # # @overload parse(*strings, partial: false) @@ -446,14 +453,14 @@ def parse *strings, partial: false # @param char [String] type_string&.each_char do |char| if char == '=' - #raise ComplexTypeError, "Invalid = in type #{type_string}" unless curly_stack > 0 + # raise ComplexTypeError, "Invalid = in type #{type_string}" unless curly_stack > 0 elsif char == '<' point_stack += 1 elsif char == '>' - if subtype_string.end_with?('=') && curly_stack > 0 + if subtype_string.end_with?('=') && curly_stack.positive? subtype_string += char elsif base.end_with?('=') - raise ComplexTypeError, "Invalid hash thing" unless key_types.nil? + raise ComplexTypeError, 'Invalid hash thing' unless key_types.nil? # types.push ComplexType.new([UniqueType.new(base[0..-2].strip)]) # @sg-ignore Need to add nil check here types.push UniqueType.parse(base[0..-2].strip, subtype_string) @@ -466,7 +473,7 @@ def parse *strings, partial: false subtype_string.clear next else - raise ComplexTypeError, "Invalid close in type #{type_string}" if point_stack == 0 + raise ComplexTypeError, "Invalid close in type #{type_string}" if point_stack.zero? point_stack -= 1 subtype_string += char end @@ -476,34 +483,37 @@ def parse *strings, partial: false elsif char == '}' curly_stack -= 1 subtype_string += char - raise ComplexTypeError, "Invalid close in type #{type_string}" if curly_stack < 0 + raise ComplexTypeError, "Invalid close in type #{type_string}" if curly_stack.negative? next elsif char == '(' paren_stack += 1 elsif char == ')' paren_stack -= 1 subtype_string += char - raise ComplexTypeError, "Invalid close in type #{type_string}" if paren_stack < 0 + raise ComplexTypeError, "Invalid close in type #{type_string}" if paren_stack.negative? next - elsif char == ',' && point_stack == 0 && curly_stack == 0 && paren_stack == 0 + elsif char == ',' && point_stack.zero? && curly_stack.zero? && paren_stack.zero? # types.push ComplexType.new([UniqueType.new(base.strip, subtype_string.strip)]) types.push UniqueType.parse(base.strip, subtype_string.strip) base.clear subtype_string.clear next end - if point_stack == 0 && curly_stack == 0 && paren_stack == 0 + if point_stack.zero? && curly_stack.zero? && paren_stack.zero? base.concat char else subtype_string.concat char end end - raise ComplexTypeError, "Unclosed subtype in #{type_string}" if point_stack != 0 || curly_stack != 0 || paren_stack != 0 + if point_stack != 0 || curly_stack != 0 || paren_stack != 0 + raise ComplexTypeError, + "Unclosed subtype in #{type_string}" + end # types.push ComplexType.new([UniqueType.new(base, subtype_string)]) types.push UniqueType.parse(base.strip, subtype_string.strip) end unless key_types.nil? - raise ComplexTypeError, "Invalid use of key/value parameters" unless partial + raise ComplexTypeError, 'Invalid use of key/value parameters' unless partial return key_types if types.empty? return [key_types, types] end @@ -515,7 +525,7 @@ def parse *strings, partial: false # @param strings [Array] # @return [ComplexType] def try_parse *strings - parse *strings + parse(*strings) rescue ComplexTypeError => e Solargraph.logger.info "Error parsing complex type `#{strings.join(', ')}`: #{e.message}" ComplexType::UNDEFINED @@ -540,9 +550,7 @@ def try_parse *strings # @param dst [String] # @return [String] def reduce_class dst - while dst =~ /^(Class|Module)\<(.*?)\>$/ - dst = dst.sub(/^(Class|Module)\$/, '') - end + dst = dst.sub(/^(Class|Module)$/, '') while dst =~ /^(Class|Module)<(.*?)>$/ dst end end diff --git a/lib/solargraph/complex_type/type_methods.rb b/lib/solargraph/complex_type/type_methods.rb index 1725189a4..5a1775c50 100644 --- a/lib/solargraph/complex_type/type_methods.rb +++ b/lib/solargraph/complex_type/type_methods.rb @@ -54,11 +54,11 @@ def duck_type? # @return [Boolean] def nil_type? - @nil_type ||= (name.casecmp('nil') == 0) + @nil_type ||= name.casecmp('nil').zero? end def tuple? - @tuple_type ||= (name == 'Tuple') || (name == 'Array' && subtypes.length >= 1 && fixed_parameters?) + @tuple ||= (name == 'Tuple') || (name == 'Array' && subtypes.length >= 1 && fixed_parameters?) end def void? @@ -87,7 +87,7 @@ def erased_variance situation = :method_call # @param generics_to_erase [Enumerable] # @return [self] - def erase_generics(generics_to_erase) + def erase_generics generics_to_erase transform do |type| if type.name == ComplexType::GENERIC_TAG_NAME if type.all_params.length == 1 && generics_to_erase.include?(type.all_params.first.to_s) @@ -142,7 +142,7 @@ def namespace @namespace ||= lambda do return 'Object' if duck_type? return 'NilClass' if nil_type? - return (name == 'Class' || name == 'Module') && !subtypes.empty? ? subtypes.first.name : name + %w[Class Module].include?(name) && !subtypes.empty? ? subtypes.first.name : name end.call end @@ -150,7 +150,7 @@ def namespace def namespace_type return ComplexType.parse('::Object') if duck_type? return ComplexType.parse('::NilClass') if nil_type? - return subtypes.first if (name == 'Class' || name == 'Module') && !subtypes.empty? + return subtypes.first if %w[Class Module].include?(name) && !subtypes.empty? self end @@ -177,30 +177,27 @@ def rooted_substring end # @return [String] - def generate_substring_from(&to_str) + def generate_substring_from &to_str key_types_str = key_types.map(&to_str).join(', ') subtypes_str = subtypes.map(&to_str).join(', ') - if key_types.none?(&:defined?) && subtypes.none?(&:defined?) - '' - elsif key_types.empty? && subtypes.empty? + if (key_types.none?(&:defined?) && subtypes.none?(&:defined?)) || + (key_types.empty? && subtypes.empty?) '' elsif hash_parameters? "{#{key_types_str} => #{subtypes_str}}" elsif fixed_parameters? "(#{subtypes_str})" + elsif name == 'Hash' + "<#{key_types_str}, #{subtypes_str}>" else - if name == 'Hash' - "<#{key_types_str}, #{subtypes_str}>" - else - "<#{key_types_str}#{subtypes_str}>" - end + "<#{key_types_str}#{subtypes_str}>" end end # @return [::Symbol] :class or :instance def scope @scope ||= :instance if duck_type? || nil_type? - @scope ||= (name == 'Class' || name == 'Module') && !subtypes.empty? ? :class : :instance + @scope ||= %w[Class Module].include?(name) && !subtypes.empty? ? :class : :instance end # @param other [Object] diff --git a/lib/solargraph/complex_type/unique_type.rb b/lib/solargraph/complex_type/unique_type.rb index fa0184fbf..067b01082 100644 --- a/lib/solargraph/complex_type/unique_type.rb +++ b/lib/solargraph/complex_type/unique_type.rb @@ -11,10 +11,6 @@ class UniqueType attr_reader :all_params, :subtypes, :key_types - protected def equality_fields - [@name, @all_params, @subtypes, @key_types] - end - # Create a UniqueType with the specified name and an optional substring. # The substring is the parameter section of a parametrized type, e.g., # for the type `Array`, the name is `Array` and the substring is @@ -25,11 +21,9 @@ class UniqueType # @param make_rooted [Boolean, nil] # @return [UniqueType] def self.parse name, substring = '', make_rooted: nil - if name.start_with?(':::') - raise ComplexTypeError, "Illegal prefix: #{name}" - end + raise ComplexTypeError, "Illegal prefix: #{name}" if name.start_with?(':::') if name.start_with?('::') - name = name[2..-1] + name = name[2..] rooted = true elsif !can_root_name?(name) rooted = true @@ -48,13 +42,17 @@ def self.parse name, substring = '', make_rooted: nil # @sg-ignore Need to add nil check here parameters_type = PARAMETERS_TYPE_BY_STARTING_TAG.fetch(substring[0]) if parameters_type == :hash - raise ComplexTypeError, "Bad hash type: name=#{name}, substring=#{substring}" unless !subs.is_a?(ComplexType) and subs.length == 2 and !subs[0].is_a?(UniqueType) and !subs[1].is_a?(UniqueType) + unless !subs.is_a?(ComplexType) && (subs.length == 2) && !subs[0].is_a?(UniqueType) && !subs[1].is_a?(UniqueType) + raise ComplexTypeError, + "Bad hash type: name=#{name}, substring=#{substring}" + end key_types.concat(subs[0].map { |u| ComplexType.new([u]) }) subtypes.concat(subs[1].map { |u| ComplexType.new([u]) }) elsif parameters_type == :list && name == 'Hash' # Treat Hash as Hash{A => B} if subs.length != 2 - raise ComplexTypeError, "Bad hash type: name=#{name}, substring=#{substring} - must have exactly two parameters" + raise ComplexTypeError, + "Bad hash type: name=#{name}, substring=#{substring} - must have exactly two parameters" end key_types.concat(subs[0].map { |u| ComplexType.new([u]) }) subtypes.concat(subs[1].map { |u| ComplexType.new([u]) }) @@ -71,9 +69,9 @@ def self.parse name, substring = '', make_rooted: nil # @param subtypes [Array] # @param rooted [Boolean] # @param parameters_type [Symbol, nil] - def initialize(name, key_types = [], subtypes = [], rooted:, parameters_type: nil) - if parameters_type.nil? - raise "You must supply parameters_type if you provide parameters" unless key_types.empty? && subtypes.empty? + def initialize name, key_types = [], subtypes = [], rooted:, parameters_type: nil + if parameters_type.nil? && !(key_types.empty? && subtypes.empty?) + raise 'You must supply parameters_type if you provide parameters' end raise "Please remove leading :: and set rooted instead - #{name.inspect}" if name.start_with?('::') @name = name @@ -95,7 +93,7 @@ def implicit_union? # @todo use api_map to establish number of generics in type; # if only one is allowed but multiple are passed in, treat # those as implicit unions - ['Hash', 'Array', 'Set', '_ToAry', 'Enumerable', '_Each'].include?(name) && parameters_type != :fixed + %w[Hash Array Set _ToAry Enumerable _Each].include?(name) && parameters_type != :fixed end def to_s @@ -175,7 +173,7 @@ def determine_non_literal_name # | `false` return name if name.empty? return 'NilClass' if name == 'nil' - return 'Boolean' if ['true', 'false'].include?(name) + return 'Boolean' if %w[true false].include?(name) return 'Symbol' if name[0] == ':' # @sg-ignore Need to add nil check here return 'String' if ['"', "'"].include?(name[0]) @@ -183,7 +181,7 @@ def determine_non_literal_name name end - def eql?(other) + def eql? other self.class == other.class && # @sg-ignore flow sensitive typing should support .class == .class @name == other.name && @@ -199,7 +197,7 @@ def eql?(other) @parameters_type == other.parameters_type end - def ==(other) + def == other eql?(other) end @@ -231,7 +229,7 @@ def parameter_variance _situation, default = :covariant # covariant # contravariant?: Proc - can be changed, so we can pass # in less specific super types - if ['Hash', 'Tuple', 'Array', 'Set', 'Enumerable'].include?(name) && fixed_parameters? + if %w[Hash Tuple Array Set Enumerable].include?(name) && fixed_parameters? :covariant else default @@ -244,7 +242,7 @@ def interface? end # @param other [UniqueType] - def erased_version_of?(other) + def erased_version_of? other name == other.name && (all_params.empty? || all_params.all?(&:undefined?)) end @@ -253,8 +251,8 @@ def erased_version_of?(other) # @param situation [:method_call, :assignment, :return_type] # @param rules [Array<:allow_subtype_skew, :allow_empty_params, :allow_reverse_match, :allow_any_match, :allow_undefined, :allow_unresolved_generic>] # @param variance [:invariant, :covariant, :contravariant] - def conforms_to?(api_map, expected, situation, rules = [], - variance: erased_variance(situation)) + def conforms_to? api_map, expected, situation, rules = [], + variance: erased_variance(situation) return true if undefined? && rules.include?(:allow_undefined) # @todo teach this to validate duck types as inferred type @@ -315,9 +313,9 @@ def to_rbs 'nil' elsif name == GENERIC_TAG_NAME all_params.first&.name - elsif ['Class', 'Module'].include?(name) + elsif %w[Class Module].include?(name) rbs_name - elsif ['Tuple', 'Array'].include?(name) && fixed_parameters? + elsif %w[Tuple Array].include?(name) && fixed_parameters? # tuples don't have a name; they're just [foo, bar, baz]. if substring == '()' # but there are no zero element tuples, so we go with an array @@ -342,7 +340,7 @@ def parameters? # @param types [Array] # @return [String] - def rbs_union(types) + def rbs_union types if types.length == 1 types.first.to_rbs else @@ -397,7 +395,8 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge end if new_binding resolved_generic_values.transform_values! do |complex_type| - complex_type.resolve_generics_from_context(generics_to_resolve, nil, resolved_generic_values: resolved_generic_values) + complex_type.resolve_generics_from_context(generics_to_resolve, nil, + resolved_generic_values: resolved_generic_values) end end # @sg-ignore flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) @@ -405,8 +404,10 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge end # @todo typechecking should complain when the method being called has no @yieldparam tag - new_key_types = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, &:key_types) - new_subtypes = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, &:subtypes) + new_key_types = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, + &:key_types) + new_subtypes = resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values, + &:subtypes) recreate(new_key_types: new_key_types, new_subtypes: new_subtypes) end @@ -415,7 +416,7 @@ def resolve_generics_from_context generics_to_resolve, context_type, resolved_ge # @param resolved_generic_values [Hash{String => ComplexType}] # @yieldreturn [Array] # @return [Array] - def resolve_param_generics_from_context(generics_to_resolve, context_type, resolved_generic_values) + def resolve_param_generics_from_context generics_to_resolve, context_type, resolved_generic_values types = yield self types.each_with_index.flat_map do |ct, i| ct.items.flat_map do |ut| @@ -423,10 +424,12 @@ def resolve_param_generics_from_context(generics_to_resolve, context_type, resol if context_params && context_params[i] type_arg = context_params[i] type_arg.map do |new_unique_context_type| - ut.resolve_generics_from_context generics_to_resolve, new_unique_context_type, resolved_generic_values: resolved_generic_values + ut.resolve_generics_from_context generics_to_resolve, new_unique_context_type, + resolved_generic_values: resolved_generic_values end else - ut.resolve_generics_from_context generics_to_resolve, nil, resolved_generic_values: resolved_generic_values + ut.resolve_generics_from_context generics_to_resolve, nil, + resolved_generic_values: resolved_generic_values end end end @@ -448,7 +451,7 @@ def resolve_generics definitions, context_type idx = definitions.generics.index(generic_name) next t if idx.nil? if context_type.parameters_type == :hash - if idx == 0 + if idx.zero? next ComplexType.new(context_type.key_types) elsif idx == 1 next ComplexType.new(context_type.subtypes) @@ -456,7 +459,7 @@ def resolve_generics definitions, context_type next ComplexType::UNDEFINED end elsif context_type.all?(&:implicit_union?) - if idx == 0 && !context_type.all_params.empty? + if idx.zero? && !context_type.all_params.empty? ComplexType.new(context_type.all_params) else ComplexType::UNDEFINED @@ -482,7 +485,7 @@ def map &block # @yieldreturn [self] # @return [Enumerable] def each &block - [self].each &block + [self].each(&block) end # @return [Array] @@ -493,10 +496,10 @@ def to_a # @param new_name [String, nil] # @param make_rooted [Boolean, nil] # @param new_key_types [Array, nil] - # @param rooted [Boolean, nil] + # @param make_rooted [Boolean, nil] # @param new_subtypes [Array, nil] # @return [self] - def recreate(new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil) + def recreate new_name: nil, make_rooted: nil, new_key_types: nil, new_subtypes: nil raise "Please remove leading :: and set rooted instead - #{new_name}" if new_name&.start_with?('::') new_name ||= name @@ -530,8 +533,10 @@ def force_rooted # @yieldparam t [UniqueType] # @yieldreturn [self] # @return [self] - def transform(new_name = nil, &transform_type) - raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" if new_name&.start_with?('::') + def transform new_name = nil, &transform_type + if new_name&.start_with?('::') + raise "Please remove leading :: and set rooted with recreate() instead - #{new_name}" + end if name == ComplexType::GENERIC_TAG_NAME # doesn't make sense to manipulate the name of the generic new_key_types = @key_types @@ -540,14 +545,15 @@ def transform(new_name = nil, &transform_type) new_key_types = @key_types.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } } new_subtypes = @subtypes.flat_map { |ct| ct.items.map { |ut| ut.transform(&transform_type) } } end - new_type = recreate(new_name: new_name || name, new_key_types: new_key_types, new_subtypes: new_subtypes, make_rooted: @rooted) + new_type = recreate(new_name: new_name || name, new_key_types: new_key_types, new_subtypes: new_subtypes, + make_rooted: @rooted) yield new_type end # Generate a ComplexType that fully qualifies this type's namespaces. # # @param api_map [ApiMap] The ApiMap that performs qualification - # @param context [String] The namespace from which to resolve names + # @param gates [Array] The namespaces from which to resolve names # @return [self, ComplexType, UniqueType] The generated ComplexType def qualify api_map, *gates transform do |t| @@ -585,7 +591,7 @@ def any? &block # @return [ComplexType] def reduce_class_type new_items = items.flat_map do |type| - next type unless ['Module', 'Class'].include?(type.name) + next type unless %w[Module Class].include?(type.name) next type if type.all_params.empty? type.all_params @@ -603,12 +609,12 @@ def rooted? end # @param name_to_check [String] - def can_root_name?(name_to_check = name) + def can_root_name? name_to_check = name self.class.can_root_name?(name_to_check) end # @param name [String] - def self.can_root_name?(name) + def self.can_root_name? name # name is not lowercase !name.empty? && name != name.downcase end @@ -625,8 +631,13 @@ def self.can_root_name?(name) '::NilClass' => UniqueType::NIL }.freeze - include Logging + + protected + + def equality_fields + [@name, @all_params, @subtypes, @key_types] + end end end end diff --git a/lib/solargraph/convention.rb b/lib/solargraph/convention.rb index 89eac82b7..5e73eb3bf 100644 --- a/lib/solargraph/convention.rb +++ b/lib/solargraph/convention.rb @@ -30,7 +30,7 @@ def self.unregister convention # @param source_map [SourceMap] # @return [Environ] - def self.for_local(source_map) + def self.for_local source_map result = Environ.new @@conventions.each do |conv| result.merge conv.local(source_map) @@ -40,7 +40,7 @@ def self.for_local(source_map) # @param doc_map [DocMap] # @return [Environ] - def self.for_global(doc_map) + def self.for_global doc_map result = Environ.new @@conventions.each do |conv| result.merge conv.global(doc_map) diff --git a/lib/solargraph/convention/data_definition.rb b/lib/solargraph/convention/data_definition.rb index 193364061..960852caa 100644 --- a/lib/solargraph/convention/data_definition.rb +++ b/lib/solargraph/convention/data_definition.rb @@ -93,7 +93,7 @@ def data_definition_node # @param attribute_node [Parser::AST::Node] # @param attribute_name [String] # @return [String, nil] - def attribute_comments(attribute_node, attribute_name) + def attribute_comments attribute_node, attribute_name data_comments = comments_for(attribute_node) return if data_comments.nil? || data_comments.empty? diff --git a/lib/solargraph/convention/data_definition/data_assignment_node.rb b/lib/solargraph/convention/data_definition/data_assignment_node.rb index cffe77494..97ef272cf 100644 --- a/lib/solargraph/convention/data_definition/data_assignment_node.rb +++ b/lib/solargraph/convention/data_definition/data_assignment_node.rb @@ -23,7 +23,7 @@ class << self # s(:args), # s(:send, nil, :bar)))) # @param node [::Parser::AST::Node] - def match?(node) + def match? node return false unless node&.type == :casgn return false if node.children[2].nil? diff --git a/lib/solargraph/convention/data_definition/data_definition_node.rb b/lib/solargraph/convention/data_definition/data_definition_node.rb index 49cf210a7..5c4b2276a 100644 --- a/lib/solargraph/convention/data_definition/data_definition_node.rb +++ b/lib/solargraph/convention/data_definition/data_definition_node.rb @@ -27,7 +27,7 @@ class << self # s(:send, nil, :bar))) # # @param node [Parser::AST::Node] - def match?(node) + def match? node return false unless node&.type == :class data_definition_node?(node.children[1]) @@ -37,7 +37,7 @@ def match?(node) # @param data_node [Parser::AST::Node] # @return [Boolean] - def data_definition_node?(data_node) + def data_definition_node? data_node return false unless data_node.is_a?(::Parser::AST::Node) return false unless data_node&.type == :send return false unless data_node.children[0]&.type == :const @@ -49,7 +49,7 @@ def data_definition_node?(data_node) end # @param node [Parser::AST::Node] - def initialize(node) + def initialize node @node = node end @@ -85,7 +85,7 @@ def data_node # @return [Array] def data_attribute_nodes # @sg-ignore Need to add nil check here - data_node.children[2..-1] + data_node.children[2..] end end end diff --git a/lib/solargraph/convention/gemfile.rb b/lib/solargraph/convention/gemfile.rb index 8484f6247..bcebe1de9 100644 --- a/lib/solargraph/convention/gemfile.rb +++ b/lib/solargraph/convention/gemfile.rb @@ -5,7 +5,7 @@ module Convention class Gemfile < Base def local source_map return EMPTY_ENVIRON unless File.basename(source_map.filename) == 'Gemfile' - @environ ||= Environ.new( + @local ||= Environ.new( requires: ['bundler'], domains: ['Bundler::Dsl'] ) diff --git a/lib/solargraph/convention/gemspec.rb b/lib/solargraph/convention/gemspec.rb index 66357ecbb..ce4ce78c7 100644 --- a/lib/solargraph/convention/gemspec.rb +++ b/lib/solargraph/convention/gemspec.rb @@ -5,7 +5,7 @@ module Convention class Gemspec < Base def local source_map return Convention::Base::EMPTY_ENVIRON unless File.basename(source_map.filename).end_with?('.gemspec') - @environ ||= Environ.new( + @local ||= Environ.new( requires: ['rubygems'], pins: [ Solargraph::Pin::Reference::Override.from_comment( diff --git a/lib/solargraph/convention/rakefile.rb b/lib/solargraph/convention/rakefile.rb index 17be306ae..c5286bd54 100644 --- a/lib/solargraph/convention/rakefile.rb +++ b/lib/solargraph/convention/rakefile.rb @@ -7,7 +7,7 @@ def local source_map basename = File.basename(source_map.filename) return EMPTY_ENVIRON unless basename.end_with?('.rake') || basename == 'Rakefile' - @environ ||= Environ.new( + @local ||= Environ.new( requires: ['rake'], domains: ['Rake::DSL'] ) diff --git a/lib/solargraph/convention/struct_definition.rb b/lib/solargraph/convention/struct_definition.rb index 7871dec00..f1d240363 100644 --- a/lib/solargraph/convention/struct_definition.rb +++ b/lib/solargraph/convention/struct_definition.rb @@ -142,7 +142,7 @@ def parse_comments # @param tag [YARD::Tags::Tag, nil] The param tag for this attribute.xtract_ # # @return [String] - def tag_string(tag) + def tag_string tag tag&.types&.join(',') || 'undefined' end @@ -150,8 +150,8 @@ def tag_string(tag) # @param for_setter [Boolean] If true, will return a @param tag instead of a @return tag # # @return [String] The formatted comment for the attribute - def attribute_comment(tag, for_setter) - return "" if tag.nil? + def attribute_comment tag, for_setter + return '' if tag.nil? suffix = "[#{tag_string(tag)}] #{tag.text}" diff --git a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb index 2816de6ed..6dcafd068 100644 --- a/lib/solargraph/convention/struct_definition/struct_assignment_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_assignment_node.rb @@ -24,7 +24,7 @@ class << self # s(:send, nil, :bar)))) # # @param node [Parser::AST::Node] - def match?(node) + def match? node return false unless node&.type == :casgn return false if node.children[2].nil? diff --git a/lib/solargraph/convention/struct_definition/struct_definition_node.rb b/lib/solargraph/convention/struct_definition/struct_definition_node.rb index 8f03f1fcb..51518a687 100644 --- a/lib/solargraph/convention/struct_definition/struct_definition_node.rb +++ b/lib/solargraph/convention/struct_definition/struct_definition_node.rb @@ -27,7 +27,7 @@ class << self # s(:send, nil, :bar))) # # @param node [Parser::AST::Node] - def match?(node) + def match? node return false unless node&.type == :class struct_definition_node?(node.children[1]) @@ -37,7 +37,7 @@ def match?(node) # @param struct_node [Parser::AST::Node] # @return [Boolean] - def struct_definition_node?(struct_node) + def struct_definition_node? struct_node return false unless struct_node.is_a?(::Parser::AST::Node) return false unless struct_node&.type == :send return false unless struct_node.children[0]&.type == :const @@ -49,7 +49,7 @@ def struct_definition_node?(struct_node) end # @param node [Parser::AST::Node] - def initialize(node) + def initialize node @node = node end diff --git a/lib/solargraph/converters/dd.rb b/lib/solargraph/converters/dd.rb index 98085da56..31c9e4b0f 100644 --- a/lib/solargraph/converters/dd.rb +++ b/lib/solargraph/converters/dd.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'nokogiri' module ReverseMarkdown diff --git a/lib/solargraph/converters/dl.rb b/lib/solargraph/converters/dl.rb index 92785622a..c4401ce87 100644 --- a/lib/solargraph/converters/dl.rb +++ b/lib/solargraph/converters/dl.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ReverseMarkdown module Converters class Dl < Base diff --git a/lib/solargraph/converters/dt.rb b/lib/solargraph/converters/dt.rb index edd420678..c7027e2ae 100644 --- a/lib/solargraph/converters/dt.rb +++ b/lib/solargraph/converters/dt.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module ReverseMarkdown module Converters class Dt < Base diff --git a/lib/solargraph/converters/misc.rb b/lib/solargraph/converters/misc.rb index e45b5bc9e..febacb9ee 100644 --- a/lib/solargraph/converters/misc.rb +++ b/lib/solargraph/converters/misc.rb @@ -1 +1,3 @@ +# frozen_string_literal: true + ReverseMarkdown::Converters.register :tt, ReverseMarkdown::Converters::Code.new diff --git a/lib/solargraph/diagnostics/rubocop.rb b/lib/solargraph/diagnostics/rubocop.rb index 5eade7592..39b79d9b7 100644 --- a/lib/solargraph/diagnostics/rubocop.rb +++ b/lib/solargraph/diagnostics/rubocop.rb @@ -17,7 +17,7 @@ class Rubocop < Base 'warning' => Severities::WARNING, 'error' => Severities::ERROR, 'fatal' => Severities::ERROR - } + }.freeze # @param source [Solargraph::Source] # @param _api_map [Solargraph::ApiMap] @@ -33,7 +33,7 @@ def diagnose source, _api_map # a time - it uses 'chdir' to read config files with ERB, # which can conflict with other chdirs. result = Solargraph::CHDIR_MUTEX.synchronize do - redirect_stdout{ runner.run(paths) } + redirect_stdout { runner.run(paths) } end return [] if result.empty? @@ -77,7 +77,7 @@ def offense_to_diagnostic off severity: SEVERITIES[off['severity']], source: 'rubocop', code: off['cop_name'], - message: off['message'].gsub(/^#{off['cop_name']}\:/, '') + message: off['message'].gsub(/^#{off['cop_name']}:/, '') } end @@ -96,22 +96,22 @@ def offense_start_position off # @param off [Hash{String => Hash{String => Integer}}] # @return [Position] def offense_ending_position off - if off['location']['start_line'] != off['location']['last_line'] - Position.new(off['location']['start_line'], 0) - else + if off['location']['start_line'] == off['location']['last_line'] start_line = off['location']['start_line'] - 1 # @type [Integer] last_column = off['location']['last_column'] line = @source.code.lines[start_line] col_off = if line.nil? || line.empty? - 1 - else - 0 - end + 1 + else + 0 + end Position.new( start_line, last_column - col_off ) + else + Position.new(off['location']['start_line'], 0) end end end diff --git a/lib/solargraph/diagnostics/rubocop_helpers.rb b/lib/solargraph/diagnostics/rubocop_helpers.rb index b306f638a..e97ca628e 100644 --- a/lib/solargraph/diagnostics/rubocop_helpers.rb +++ b/lib/solargraph/diagnostics/rubocop_helpers.rb @@ -13,7 +13,7 @@ module RubocopHelpers # @param version [String, nil] # @raise [InvalidRubocopVersionError] if _version_ is not installed # @return [void] - def require_rubocop(version = nil) + def require_rubocop version = nil begin # @type [String] gem_path = Gem::Specification.find_by_name('rubocop', version).full_gem_path @@ -24,7 +24,7 @@ def require_rubocop(version = nil) # @type [Array] specs = e.specs raise InvalidRubocopVersionError, - "could not find '#{e.name}' (#{e.requirement}) - "\ + "could not find '#{e.name}' (#{e.requirement}) - " \ "did find: [#{specs.map { |s| s.version.version }.join(', ')}]" end require 'rubocop' @@ -52,7 +52,7 @@ def generate_options filename, code def fix_drive_letter path return path unless path.match(/^[a-z]:/) # @sg-ignore Need to add nil check here - path[0].upcase + path[1..-1] + path[0].upcase + path[1..] end # @todo This is a smelly way to redirect output, but the RuboCop specs do diff --git a/lib/solargraph/diagnostics/type_check.rb b/lib/solargraph/diagnostics/type_check.rb index ea833860b..b1333f9d9 100644 --- a/lib/solargraph/diagnostics/type_check.rb +++ b/lib/solargraph/diagnostics/type_check.rb @@ -10,19 +10,19 @@ class TypeCheck < Base def diagnose source, api_map # return [] unless args.include?('always') || api_map.workspaced?(source.filename) severity = Diagnostics::Severities::ERROR - level = (args.reverse.find { |a| ['normal', 'typed', 'strict', 'strong'].include?(a) }) || :normal + level = args.reverse.find { |a| %w[normal typed strict strong].include?(a) } || :normal # @sg-ignore sensitive typing needs to handle || on nil types checker = Solargraph::TypeChecker.new(source.filename, api_map: api_map, level: level.to_sym) checker.problems - .sort { |a, b| a.location.range.start.line <=> b.location.range.start.line } - .map do |problem| - { - range: extract_first_line(problem.location, source), - severity: severity, - source: 'Typecheck', - message: problem.message - } - end + .sort { |a, b| a.location.range.start.line <=> b.location.range.start.line } + .map do |problem| + { + range: extract_first_line(problem.location, source), + severity: severity, + source: 'Typecheck', + message: problem.message + } + end end private diff --git a/lib/solargraph/diagnostics/update_errors.rb b/lib/solargraph/diagnostics/update_errors.rb index b6f9baa89..c2ca02408 100644 --- a/lib/solargraph/diagnostics/update_errors.rb +++ b/lib/solargraph/diagnostics/update_errors.rb @@ -4,16 +4,12 @@ module Solargraph module Diagnostics class UpdateErrors < Base def diagnose source, api_map - result = [] - combine_ranges(source.code, source.error_ranges).each do |range| - result.push( - range: range.to_hash, + combine_ranges(source.code, source.error_ranges).map do |range| + { range: range.to_hash, severity: Diagnostics::Severities::ERROR, source: 'Solargraph', - message: 'Syntax error' - ) + message: 'Syntax error' } end - result end private @@ -26,7 +22,7 @@ def diagnose source, api_map def combine_ranges code, ranges result = [] lines = [] - ranges.sort{|a, b| a.start.line <=> b.start.line}.each do |rng| + ranges.sort { |a, b| a.start.line <=> b.start.line }.each do |rng| next if rng.nil? || lines.include?(rng.start.line) lines.push rng.start.line next if rng.start.line >= code.lines.length diff --git a/lib/solargraph/doc_map.rb b/lib/solargraph/doc_map.rb index 79bb086cc..61589e016 100644 --- a/lib/solargraph/doc_map.rb +++ b/lib/solargraph/doc_map.rb @@ -98,12 +98,12 @@ def dependencies out: $stderr @dependencies ||= begin gem_deps = gemspecs - .flat_map { |spec| workspace.fetch_dependencies(spec, out: out) } - .uniq(&:name) + .flat_map { |spec| workspace.fetch_dependencies(spec, out: out) } + .uniq(&:name) stdlib_deps = gemspecs - .flat_map { |spec| workspace.stdlib_dependencies(spec.name) } - .flat_map { |dep_name| workspace.resolve_require(dep_name) } - .compact + .flat_map { |spec| workspace.stdlib_dependencies(spec.name) } + .flat_map { |dep_name| workspace.resolve_require(dep_name) } + .compact existing_gems = gemspecs.map(&:name) (gem_deps + stdlib_deps).reject { |gemspec| existing_gems.include? gemspec.name } end @@ -134,11 +134,10 @@ def gemspecs def load_serialized_gem_pins out: @out serialized_pins = [] with_gemspecs, without_gemspecs = required_gems_map.partition { |_, v| v } - # @sg-ignore Need better typing for Hash[] # @type [Array] - missing_paths = Hash[without_gemspecs].keys + missing_paths = without_gemspecs.to_h.keys # @type [Array] - gemspecs = Hash[with_gemspecs].values.flatten.compact + dependencies(out: out).to_a + gemspecs = with_gemspecs.to_h.values.flatten.compact + dependencies(out: out).to_a # if we are type checking a gem project, we should not include # pins from rbs or yard from that gem here - we use our own @@ -167,7 +166,7 @@ def load_serialized_gem_pins out: @out end end - existing_pin_count = serialized_pins.length + serialized_pins.length time = Benchmark.measure do gemspecs.each do |gemspec| # only deserializes already-cached gems @@ -179,7 +178,7 @@ def load_serialized_gem_pins out: @out end end end - pins_processed = serialized_pins.length - existing_pin_count + serialized_pins.length milliseconds = (time.real * 1000).round if (milliseconds > 500) && out && gemspecs.any? out.puts "Deserialized #{serialized_pins.length} gem pins from #{PinCache.base_dir} in #{milliseconds} ms" diff --git a/lib/solargraph/equality.rb b/lib/solargraph/equality.rb index edef8fa7a..0a266df79 100644 --- a/lib/solargraph/equality.rb +++ b/lib/solargraph/equality.rb @@ -10,7 +10,7 @@ module Equality # @param other [Object] # @return [Boolean] - def eql?(other) + def eql? other self.class.eql?(other.class) && # @sg-ignore flow sensitive typing should support .class == .class equality_fields.eql?(other.equality_fields) @@ -18,8 +18,8 @@ def eql?(other) # @param other [Object] # @return [Boolean] - def ==(other) - self.eql?(other) + def == other + eql?(other) end def hash diff --git a/lib/solargraph/gem_pins.rb b/lib/solargraph/gem_pins.rb index 790422065..9be1e8eb7 100644 --- a/lib/solargraph/gem_pins.rb +++ b/lib/solargraph/gem_pins.rb @@ -13,8 +13,8 @@ class << self # @param pins [Array] # @return [Array] - def self.combine_method_pins_by_path(pins) - method_pins, alias_pins = pins.partition { |pin| pin.class == Pin::Method } + def self.combine_method_pins_by_path pins + method_pins, alias_pins = pins.partition { |pin| pin.instance_of?(Pin::Method) } by_path = method_pins.group_by(&:path) by_path.transform_values! do |pins| GemPins.combine_method_pins(*pins) @@ -47,7 +47,7 @@ def self.combine_method_pins(*pins) # @param rbs_pins [Array] # # @return [Array] - def self.combine(yard_pins, rbs_pins) + def self.combine yard_pins, rbs_pins in_yard = Set.new rbs_store = Solargraph::ApiMap::Store.new(rbs_pins) combined = yard_pins.map do |yard_pin| @@ -57,7 +57,9 @@ def self.combine(yard_pins, rbs_pins) next yard_pin unless rbs_pin && yard_pin.is_a?(Pin::Method) unless rbs_pin - logger.debug { "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" } + logger.debug do + "GemPins.combine: No rbs pin for #{yard_pin.path} - using YARD's '#{yard_pin.inspect} (return_type=#{yard_pin.return_type}; signatures=#{yard_pin.signatures})" + end next yard_pin end @@ -90,7 +92,7 @@ class << self # @param choices [Array] # @return [ComplexType] def best_return_type *choices - choices.find { |pin| pin.defined? } || choices.first || ComplexType::UNDEFINED + choices.find(&:defined?) || choices.first || ComplexType::UNDEFINED end end end diff --git a/lib/solargraph/language_server/error_codes.rb b/lib/solargraph/language_server/error_codes.rb index 492ca6462..7df65388a 100644 --- a/lib/solargraph/language_server/error_codes.rb +++ b/lib/solargraph/language_server/error_codes.rb @@ -5,16 +5,16 @@ module LanguageServer # The ErrorCode constants for the language server protocol. # module ErrorCodes - PARSE_ERROR = -32700 - INVALID_REQUEST = -32600 - METHOD_NOT_FOUND = -32601 - INVALID_PARAMS = -32602 - INTERNAL_ERROR = -32603 - SERVER_ERROR_START = -32099 - SERVER_ERROR_END = -32000 - SERVER_NOT_INITIALIZED = -32002 - UNKNOWN_ERROR_CODE = -32001 - REQUEST_CANCELLED = -32800 + PARSE_ERROR = -32_700 + INVALID_REQUEST = -32_600 + METHOD_NOT_FOUND = -32_601 + INVALID_PARAMS = -32_602 + INTERNAL_ERROR = -32_603 + SERVER_ERROR_START = -32_099 + SERVER_ERROR_END = -32_000 + SERVER_NOT_INITIALIZED = -32_002 + UNKNOWN_ERROR_CODE = -32_001 + REQUEST_CANCELLED = -32_800 end end end diff --git a/lib/solargraph/language_server/host.rb b/lib/solargraph/language_server/host.rb index c59f7cd5e..d61283567 100644 --- a/lib/solargraph/language_server/host.rb +++ b/lib/solargraph/language_server/host.rb @@ -53,7 +53,7 @@ def configure update logger.level = LOG_LEVELS[options['logLevel']] || DEFAULT_LOG_LEVEL end - # @return [Hash{String => [Boolean, String]}] + # @return [Hash{String => Boolean, String}] def options @options ||= default_configuration end @@ -119,7 +119,7 @@ def receive request nil end else - logger.warn "Invalid message received." + logger.warn 'Invalid message received.' logger.debug request nil end @@ -154,7 +154,7 @@ def delete *uris lib.delete(*filenames) end uris.each do |uri| - send_notification "textDocument/publishDiagnostics", { + send_notification 'textDocument/publishDiagnostics', { uri: uri, diagnostics: [] } @@ -209,7 +209,7 @@ def diagnose uri logger.info "Diagnosing #{uri}" begin results = library.diagnose uri_to_file(uri) - send_notification "textDocument/publishDiagnostics", { + send_notification 'textDocument/publishDiagnostics', { uri: uri, diagnostics: results } @@ -354,7 +354,7 @@ def folders # @return [void] def send_notification method, params response = { - jsonrpc: "2.0", + jsonrpc: '2.0', method: method, params: params } @@ -377,7 +377,7 @@ def send_notification method, params def send_request method, params, &block @request_mutex.synchronize do message = { - jsonrpc: "2.0", + jsonrpc: '2.0', method: method, params: params, id: @next_request_id @@ -419,13 +419,13 @@ def register_capabilities methods # @return [void] def unregister_capabilities methods logger.debug "Unregistering capabilities: #{methods}" - unregisterations = methods.select{|m| registered?(m)}.map{ |m| + unregisterations = methods.select { |m| registered?(m) }.map do |m| @registered_capabilities.delete m { id: m, method: m } - } + end return if unregisterations.empty? send_request 'client/unregisterCapability', { unregisterations: unregisterations } end @@ -493,7 +493,7 @@ def locate_pins params params['data']['location']['range']['end']['character'] ) ) - result.concat library.locate_pins(location).select{ |pin| pin.name == params['label'] } + result.concat(library.locate_pins(location).select { |pin| pin.name == params['label'] }) end if params['data']['path'] result.concat library.path_pins(params['data']['path']) @@ -547,7 +547,7 @@ def completions_at uri, line, column end # @return [Bool] if has pending completion request - def has_pending_completions? + def pending_completions? message_worker.messages.reverse_each.any? { |req| req['method'] == 'textDocument/completion' } end @@ -648,7 +648,7 @@ def show_message text, type = LanguageServer::MessageTypes::INFO # @param text [String] # @param type [Integer] A MessageType constant # @param actions [Array] Response options for the client - # @param block The block that processes the response + # @param block [Proc] The block that processes the response # @yieldparam [String] The action received from the client # @return [void] def show_message_request text, type, actions, &block @@ -667,7 +667,7 @@ def pending_requests requests.keys end - # @return [Hash{String => [Boolean,String]}] + # @return [Hash{String => Boolean,String}] def default_configuration { 'completion' => true, @@ -748,9 +748,12 @@ def generate_updater params params['contentChanges'].each do |recvd| chng = check_diff(params['textDocument']['uri'], recvd) changes.push Solargraph::Source::Change.new( - (chng['range'].nil? ? - nil : - Solargraph::Range.from_to(chng['range']['start']['line'], chng['range']['start']['character'], chng['range']['end']['line'], chng['range']['end']['character']) + (if chng['range'].nil? + nil + else + Solargraph::Range.from_to(chng['range']['start']['line'], chng['range']['start']['character'], + chng['range']['end']['line'], chng['range']['end']['character']) + end ), chng['text'] ) @@ -770,7 +773,7 @@ def check_diff uri, change source = sources.find(uri) return change if source.code.length + 1 != change['text'].length diffs = Diff::LCS.diff(source.code, change['text']) - return change if diffs.length.zero? || diffs.length > 1 || diffs.first.length > 1 + return change if diffs.empty? || diffs.length > 1 || diffs.first.length > 1 # @type [Diff::LCS::Change] diff = diffs.first.first return change unless diff.adding? && ['.', ':', '(', ',', ' '].include?(diff.element) @@ -813,10 +816,10 @@ def dynamic_capability_options # documentSymbolProvider: true, # workspaceSymbolProvider: true, # workspace: { - # workspaceFolders: { - # supported: true, - # changeNotifications: true - # } + # workspaceFolders: { + # supported: true, + # changeNotifications: true + # } # } 'textDocument/definition' => { definitionProvider: true @@ -863,7 +866,7 @@ def library_map library end # @param library [Library] - # @param uuid [String, nil] + # # @return [void] def sync_library_map library total = library.workspace.sources.length diff --git a/lib/solargraph/language_server/host/diagnoser.rb b/lib/solargraph/language_server/host/diagnoser.rb index e69ae16f9..8c259c131 100644 --- a/lib/solargraph/language_server/host/diagnoser.rb +++ b/lib/solargraph/language_server/host/diagnoser.rb @@ -56,7 +56,7 @@ def start # @return [void] def tick return if queue.empty? || host.synchronizing? - if !host.options['diagnostics'] + unless host.options['diagnostics'] mutex.synchronize { queue.clear } return end diff --git a/lib/solargraph/language_server/host/dispatch.rb b/lib/solargraph/language_server/host/dispatch.rb index f64b4ac96..b3c78ffca 100644 --- a/lib/solargraph/language_server/host/dispatch.rb +++ b/lib/solargraph/language_server/host/dispatch.rb @@ -45,12 +45,11 @@ def update_libraries uri # @param uri [String] # @return [Library] def library_for uri - result = explicit_library_for(uri) || + explicit_library_for(uri) || implicit_library_for(uri) || generic_library_for(uri) # previous library for already call attach. avoid call twice # result.attach sources.find(uri) if sources.include?(uri) - result end # Find an explicit library match for the given URI. An explicit match @@ -119,8 +118,8 @@ def generic_library .tap { |lib| lib.add_observer self } end - # @param library [Solargraph::Library] # @param progress [Solargraph::LanguageServer::Progress, nil] + # # @return [void] def update progress progress&.send(self) diff --git a/lib/solargraph/language_server/host/message_worker.rb b/lib/solargraph/language_server/host/message_worker.rb index b0878b154..440e2c7cc 100644 --- a/lib/solargraph/language_server/host/message_worker.rb +++ b/lib/solargraph/language_server/host/message_worker.rb @@ -20,7 +20,7 @@ class MessageWorker ].freeze # @param host [Host] - def initialize(host) + def initialize host @host = host @mutex = Mutex.new @resource = ConditionVariable.new @@ -44,7 +44,7 @@ def stop # @param message [Hash] The message to handle. Will be forwarded to Host#receive # @return [void] - def queue(message) + def queue message @mutex.synchronize do messages.push(message) @resource.signal diff --git a/lib/solargraph/language_server/host/sources.rb b/lib/solargraph/language_server/host/sources.rb index 766ea634a..72a155d73 100644 --- a/lib/solargraph/language_server/host/sources.rb +++ b/lib/solargraph/language_server/host/sources.rb @@ -13,7 +13,7 @@ class Sources # @param uri [String] # @return [void] - def add_uri(uri) + def add_uri uri queue.push(uri) end diff --git a/lib/solargraph/language_server/message.rb b/lib/solargraph/language_server/message.rb index ab74c2eb4..170bfdb4f 100644 --- a/lib/solargraph/language_server/message.rb +++ b/lib/solargraph/language_server/message.rb @@ -38,7 +38,7 @@ def register path, message_class # @param path [String] # @return [Class] def select path - if method_map.has_key?(path) + if method_map.key?(path) method_map[path] elsif path.start_with?('$/') MethodNotImplemented diff --git a/lib/solargraph/language_server/message/base.rb b/lib/solargraph/language_server/message/base.rb index cc72d99b5..6b61101c4 100644 --- a/lib/solargraph/language_server/message/base.rb +++ b/lib/solargraph/language_server/message/base.rb @@ -69,7 +69,7 @@ def send_response } response[:result] = result unless result.nil? response[:error] = error unless error.nil? - response[:result] = nil if result.nil? and error.nil? + response[:result] = nil if result.nil? && error.nil? json = response.to_json envelope = "Content-Length: #{json.bytesize}\r\n\r\n#{json}" Solargraph.logger.debug envelope diff --git a/lib/solargraph/language_server/message/client/register_capability.rb b/lib/solargraph/language_server/message/client/register_capability.rb index a9af8748e..67469def8 100644 --- a/lib/solargraph/language_server/message/client/register_capability.rb +++ b/lib/solargraph/language_server/message/client/register_capability.rb @@ -5,9 +5,7 @@ module LanguageServer module Message module Client class RegisterCapability < Solargraph::LanguageServer::Message::Base - def process - - end + def process; end end end end diff --git a/lib/solargraph/language_server/message/completion_item/resolve.rb b/lib/solargraph/language_server/message/completion_item/resolve.rb index 85e03ad4f..83cc5c1fe 100644 --- a/lib/solargraph/language_server/message/completion_item/resolve.rb +++ b/lib/solargraph/language_server/message/completion_item/resolve.rb @@ -21,9 +21,9 @@ def merge pins docs = pins .reject { |pin| pin.documentation.empty? && pin.return_type.undefined? } result = params - .transform_keys(&:to_sym) - .merge(pins.first.resolve_completion_item) - .merge(documentation: markup_content(join_docs(docs))) + .transform_keys(&:to_sym) + .merge(pins.first.resolve_completion_item) + .merge(documentation: markup_content(join_docs(docs))) result[:detail] = pins.first.detail result end @@ -43,12 +43,10 @@ def markup_content text def join_docs pins result = [] last_link = nil - pins.each_with_index do |pin| + pins.each do |pin| this_link = host.options['enablePages'] ? pin.link_documentation : pin.text_documentation - if this_link && this_link != last_link && this_link != 'undefined' - result.push this_link - end - result.push pin.documentation unless result.last && result.last.end_with?(pin.documentation) + result.push this_link if this_link && this_link != last_link && this_link != 'undefined' + result.push pin.documentation unless result.last&.end_with?(pin.documentation) last_link = this_link end result.join("\n\n") diff --git a/lib/solargraph/language_server/message/extended/check_gem_version.rb b/lib/solargraph/language_server/message/extended/check_gem_version.rb index 4a6a3df4d..8909406a4 100644 --- a/lib/solargraph/language_server/message/extended/check_gem_version.rb +++ b/lib/solargraph/language_server/message/extended/check_gem_version.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true - module Solargraph module LanguageServer module Message @@ -17,8 +16,8 @@ def self.fetcher # @param obj [Gem::SpecFetcher] # @return [Gem::SpecFetcher] - def self.fetcher= obj - @fetcher = obj + class << self + attr_writer :fetcher end GEM_ZERO = Gem::Version.new('0.0.0') @@ -41,11 +40,11 @@ def process ['Update now'] do |result| next unless result == 'Update now' cmd = if host.options['useBundler'] - 'bundle update solargraph' - else - 'gem update solargraph' - end - o, s = Open3.capture2(cmd) + 'bundle update solargraph' + else + 'gem update solargraph' + end + _, s = Open3.capture2(cmd) if s == 0 host.show_message 'Successfully updated the Solargraph gem.', LanguageServer::MessageTypes::INFO host.send_notification '$/solargraph/restart', {} @@ -62,9 +61,9 @@ def process host.show_message(error, MessageTypes::ERROR) if params['verbose'] end set_result({ - installed: current, - available: available - }) + installed: current, + available: available + }) end private diff --git a/lib/solargraph/language_server/message/extended/document_gems.rb b/lib/solargraph/language_server/message/extended/document_gems.rb index f1cfca0b8..403632390 100644 --- a/lib/solargraph/language_server/message/extended/document_gems.rb +++ b/lib/solargraph/language_server/message/extended/document_gems.rb @@ -13,16 +13,16 @@ class DocumentGems < Base def process cmd = [host.command_path, 'gems'] cmd.push '--rebuild' if params['rebuild'] - o, s = Open3.capture2(*cmd) - if s != 0 - host.show_message "An error occurred while building gem documentation.", LanguageServer::MessageTypes::ERROR + _, s = Open3.capture2(*cmd) + if s == 0 set_result({ - status: 'err' - }) + status: 'ok' + }) else + host.show_message 'An error occurred while building gem documentation.', LanguageServer::MessageTypes::ERROR set_result({ - status: 'ok' - }) + status: 'err' + }) end end end diff --git a/lib/solargraph/language_server/message/extended/download_core.rb b/lib/solargraph/language_server/message/extended/download_core.rb index 7310757a1..4e0775c14 100644 --- a/lib/solargraph/language_server/message/extended/download_core.rb +++ b/lib/solargraph/language_server/message/extended/download_core.rb @@ -10,7 +10,8 @@ module Extended # class DownloadCore < Base def process - host.show_message "Downloading cores is deprecated. Solargraph currently uses RBS for core and stdlib documentation", LanguageServer::MessageTypes::INFO + host.show_message 'Downloading cores is deprecated. Solargraph currently uses RBS for core and stdlib documentation', + LanguageServer::MessageTypes::INFO end end end diff --git a/lib/solargraph/language_server/message/extended/search.rb b/lib/solargraph/language_server/message/extended/search.rb index 1f09a8890..312c048bb 100644 --- a/lib/solargraph/language_server/message/extended/search.rb +++ b/lib/solargraph/language_server/message/extended/search.rb @@ -8,7 +8,7 @@ class Search < Base def process results = host.search(params['query']) page = Solargraph::Page.new(host.options['viewsPath']) - content = page.render('search', layout: true, locals: {query: params['query'], results: results}) + content = page.render('search', layout: true, locals: { query: params['query'], results: results }) set_result( content: content ) diff --git a/lib/solargraph/language_server/message/initialize.rb b/lib/solargraph/language_server/message/initialize.rb index 3f3e1338b..f0f54f22e 100644 --- a/lib/solargraph/language_server/message/initialize.rb +++ b/lib/solargraph/language_server/message/initialize.rb @@ -26,20 +26,27 @@ def process } } # FIXME: lsp default is utf-16, may have different position - result[:capabilities][:positionEncoding] = "utf-32" if params.dig("capabilities", "general", "positionEncodings")&.include?("utf-32") + result[:capabilities][:positionEncoding] = 'utf-32' if params.dig('capabilities', 'general', + 'positionEncodings')&.include?('utf-32') result[:capabilities].merge! static_completion unless dynamic_registration_for?('textDocument', 'completion') - result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', 'signatureHelp') + result[:capabilities].merge! static_signature_help unless dynamic_registration_for?('textDocument', + 'signatureHelp') # result[:capabilities].merge! static_on_type_formatting unless dynamic_registration_for?('textDocument', 'onTypeFormatting') result[:capabilities].merge! static_hover unless dynamic_registration_for?('textDocument', 'hover') - result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', 'formatting') - result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', 'documentSymbol') + result[:capabilities].merge! static_document_formatting unless dynamic_registration_for?('textDocument', + 'formatting') + result[:capabilities].merge! static_document_symbols unless dynamic_registration_for?('textDocument', + 'documentSymbol') result[:capabilities].merge! static_definitions unless dynamic_registration_for?('textDocument', 'definition') - result[:capabilities].merge! static_type_definitions unless dynamic_registration_for?('textDocument', 'typeDefinition') + result[:capabilities].merge! static_type_definitions unless dynamic_registration_for?('textDocument', + 'typeDefinition') result[:capabilities].merge! static_rename unless dynamic_registration_for?('textDocument', 'rename') result[:capabilities].merge! static_references unless dynamic_registration_for?('textDocument', 'references') result[:capabilities].merge! static_workspace_symbols unless dynamic_registration_for?('workspace', 'symbol') - result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', 'foldingRange') - result[:capabilities].merge! static_highlights unless dynamic_registration_for?('textDocument', 'documentHighlight') + result[:capabilities].merge! static_folding_range unless dynamic_registration_for?('textDocument', + 'foldingRange') + result[:capabilities].merge! static_highlights unless dynamic_registration_for?('textDocument', + 'documentHighlight') # @todo Temporarily disabled # result[:capabilities].merge! static_code_action unless dynamic_registration_for?('textDocument', 'codeAction') set_result result @@ -71,7 +78,7 @@ def static_completion def static_code_action { codeActionProvider: true, - codeActionKinds: ["quickfix"] + codeActionKinds: ['quickfix'] } end @@ -144,11 +151,10 @@ def static_type_definitions # @return [Hash{Symbol => Hash{Symbol => Boolean}}] def static_rename { - renameProvider: {prepareProvider: true} + renameProvider: { prepareProvider: true } } end - # @return [Hash{Symbol => Boolean}] def static_references return {} unless host.options['references'] @@ -178,10 +184,10 @@ def static_highlights # enforce strict true/false-ness # @sg-ignore def dynamic_registration_for? section, capability - result = (params['capabilities'] && - params['capabilities'][section] && - params['capabilities'][section][capability] && - params['capabilities'][section][capability]['dynamicRegistration']) + result = params['capabilities'] && + params['capabilities'][section] && + params['capabilities'][section][capability] && + params['capabilities'][section][capability]['dynamicRegistration'] host.allow_registration("#{section}/#{capability}") if result result end diff --git a/lib/solargraph/language_server/message/text_document/completion.rb b/lib/solargraph/language_server/message/text_document/completion.rb index 5b9acec33..d2f352a3d 100644 --- a/lib/solargraph/language_server/message/text_document/completion.rb +++ b/lib/solargraph/language_server/message/text_document/completion.rb @@ -6,7 +6,7 @@ module Message module TextDocument class Completion < Base def process - return set_error(ErrorCodes::REQUEST_CANCELLED, "cancelled by so many request") if host.has_pending_completions? + return set_error(ErrorCodes::REQUEST_CANCELLED, 'cancelled by so many request') if host.pending_completions? line = params['position']['line'] col = params['position']['character'] @@ -19,12 +19,12 @@ def process completion.pins.each do |pin| idx += 1 if last_context != pin.context items.push pin.completion_item.merge({ - textEdit: { - range: completion.range.to_hash, - newText: pin.name.sub(/=$/, ' = ').sub(/:$/, ': ') - }, - sortText: "#{idx.to_s.rjust(4, '0')}#{pin.name}" - }) + textEdit: { + range: completion.range.to_hash, + newText: pin.name.sub(/=$/, ' = ').sub(/:$/, ': ') + }, + sortText: "#{idx.to_s.rjust(4, '0')}#{pin.name}" + }) items.last[:data][:uri] = params['textDocument']['uri'] last_context = pin.context end @@ -32,7 +32,7 @@ def process isIncomplete: false, items: items ) - rescue InvalidOffsetError => e + rescue InvalidOffsetError Logging.logger.info "Completion ignored invalid offset: #{params['textDocument']['uri']}, line #{line}, character #{col}" set_result empty_result end diff --git a/lib/solargraph/language_server/message/text_document/definition.rb b/lib/solargraph/language_server/message/text_document/definition.rb index 96c1e988a..1bac9b36c 100644 --- a/lib/solargraph/language_server/message/text_document/definition.rb +++ b/lib/solargraph/language_server/message/text_document/definition.rb @@ -1,42 +1,49 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class Definition < Base - def process - @line = params['position']['line'] - @column = params['position']['character'] - set_result(code_location || require_location || []) - end +module Solargraph + module LanguageServer + module Message + module TextDocument + class Definition < Base + def process + @line = params['position']['line'] + @column = params['position']['character'] + set_result(code_location || require_location || []) + end - private + private - # @return [Array, nil] - def code_location - suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) - # @sg-ignore Need to add nil check here - return nil if suggestions.empty? - # @sg-ignore Need to add nil check here - suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| - { - uri: file_to_uri(pin.best_location.filename), - range: pin.best_location.range.to_hash - } - end - end + # @return [Array, nil] + def code_location + suggestions = host.definitions_at(params['textDocument']['uri'], @line, @column) + # @sg-ignore Need to add nil check here + return nil if suggestions.empty? + # @sg-ignore Need to add nil check here + suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| + { + uri: file_to_uri(pin.best_location.filename), + range: pin.best_location.range.to_hash + } + end + end - # @return [Array, nil] - def require_location - # @todo Terrible hack - lib = host.library_for(params['textDocument']['uri']) - rloc = Solargraph::Location.new(uri_to_file(params['textDocument']['uri']), Solargraph::Range.from_to(@line, @column, @line, @column)) - dloc = lib.locate_ref(rloc) - return nil if dloc.nil? - [ - { - uri: file_to_uri(dloc.filename), - range: dloc.range.to_hash - } - ] + # @return [Array, nil] + def require_location + # @todo Terrible hack + lib = host.library_for(params['textDocument']['uri']) + rloc = Solargraph::Location.new(uri_to_file(params['textDocument']['uri']), + Solargraph::Range.from_to(@line, @column, @line, @column)) + dloc = lib.locate_ref(rloc) + return nil if dloc.nil? + [ + { + uri: file_to_uri(dloc.filename), + range: dloc.range.to_hash + } + ] + end + end + end end end end diff --git a/lib/solargraph/language_server/message/text_document/document_highlight.rb b/lib/solargraph/language_server/message/text_document/document_highlight.rb index 2aded7641..e1ecfcb29 100644 --- a/lib/solargraph/language_server/message/text_document/document_highlight.rb +++ b/lib/solargraph/language_server/message/text_document/document_highlight.rb @@ -1,16 +1,23 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class DocumentHighlight < Base - def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], params['position']['character'], strip: true, only: true) - result = locs.map do |loc| - { - range: loc.range.to_hash, - kind: 1 - } +module Solargraph + module LanguageServer + module Message + module TextDocument + class DocumentHighlight < Base + def process + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character'], strip: true, only: true) + result = locs.map do |loc| + { + range: loc.range.to_hash, + kind: 1 + } + end + set_result result + end + end end - set_result result end end end diff --git a/lib/solargraph/language_server/message/text_document/document_symbol.rb b/lib/solargraph/language_server/message/text_document/document_symbol.rb index 3d3cccbde..04b706358 100644 --- a/lib/solargraph/language_server/message/text_document/document_symbol.rb +++ b/lib/solargraph/language_server/message/text_document/document_symbol.rb @@ -1,28 +1,36 @@ # frozen_string_literal: true -class Solargraph::LanguageServer::Message::TextDocument::DocumentSymbol < Solargraph::LanguageServer::Message::Base - include Solargraph::LanguageServer::UriHelpers +module Solargraph + module LanguageServer + module Message + module TextDocument + class DocumentSymbol < Solargraph::LanguageServer::Message::Base + include Solargraph::LanguageServer::UriHelpers - def process - pins = host.document_symbols params['textDocument']['uri'] - info = pins.map do |pin| - next nil unless pin.best_location&.filename + def process + pins = host.document_symbols params['textDocument']['uri'] + info = pins.map do |pin| + next nil unless pin.best_location&.filename - result = { - name: pin.name, - containerName: pin.namespace, - kind: pin.symbol_kind, - location: { - # @sg-ignore Need to add nil check here - uri: file_to_uri(pin.best_location.filename), - # @sg-ignore Need to add nil check here - range: pin.best_location.range.to_hash - }, - deprecated: pin.deprecated? - } - result - end.compact + result = { + name: pin.name, + containerName: pin.namespace, + kind: pin.symbol_kind, + location: { + # @sg-ignore Need to add nil check here + uri: file_to_uri(pin.best_location.filename), + # @sg-ignore Need to add nil check here + range: pin.best_location.range.to_hash + }, + deprecated: pin.deprecated? + } + result + end.compact - set_result info + set_result info + end + end + end + end end end diff --git a/lib/solargraph/language_server/message/text_document/formatting.rb b/lib/solargraph/language_server/message/text_document/formatting.rb index 7010de741..c6cc3353a 100644 --- a/lib/solargraph/language_server/message/text_document/formatting.rb +++ b/lib/solargraph/language_server/message/text_document/formatting.rb @@ -43,7 +43,7 @@ def process # @param corrections [String] # @return [void] - def log_corrections(corrections) + def log_corrections corrections corrections = corrections&.strip return if corrections&.empty? @@ -56,7 +56,7 @@ def log_corrections(corrections) # @param file_uri [String] # @return [Hash{String => undefined}] - def config_for(file_uri) + def config_for file_uri conf = host.formatter_config(file_uri) return {} unless conf.is_a?(Hash) @@ -71,10 +71,10 @@ def cli_args file_uri, config args = [ config['cops'] == 'all' ? '-A' : '-a', '--cache', 'false', - '--format', formatter_class(config).name, + '--format', formatter_class(config).name ] - ['except', 'only'].each do |arg| + %w[except only].each do |arg| cops = cop_list(config[arg]) args += ["--#{arg}", cops] if cops end @@ -86,7 +86,7 @@ def cli_args file_uri, config # @param config [Hash{String => String}] # @sg-ignore # @return [Class] - def formatter_class(config) + def formatter_class config if self.class.const_defined?('BlankRubocopFormatter') # @sg-ignore BlankRubocopFormatter @@ -100,7 +100,7 @@ def formatter_class(config) # @param value [Array, String] # # @return [String, nil] - def cop_list(value) + def cop_list value # @type [String] # @sg-ignore Translate to something flow sensitive typing understands value = value.join(',') if value.respond_to?(:join) diff --git a/lib/solargraph/language_server/message/text_document/hover.rb b/lib/solargraph/language_server/message/text_document/hover.rb index 57a9161a3..6b60969e1 100644 --- a/lib/solargraph/language_server/message/text_document/hover.rb +++ b/lib/solargraph/language_server/message/text_document/hover.rb @@ -15,9 +15,7 @@ def process suggestions.each do |pin| parts = [] this_link = host.options['enablePages'] ? pin.link_documentation : pin.text_documentation - if !this_link.nil? && this_link != last_link - parts.push this_link - end + parts.push this_link if !this_link.nil? && this_link != last_link parts.push "`#{pin.detail}`" unless pin.is_a?(Pin::Namespace) || pin.detail.nil? parts.push pin.documentation unless pin.documentation.nil? || pin.documentation.empty? unless parts.empty? @@ -43,8 +41,8 @@ def process # @return [Hash{Symbol => Hash{Symbol => String}}, nil] def contents_or_nil contents stripped = contents - .map(&:strip) - .reject { |c| c.empty? } + .map(&:strip) + .reject(&:empty?) return nil if stripped.empty? { contents: { diff --git a/lib/solargraph/language_server/message/text_document/prepare_rename.rb b/lib/solargraph/language_server/message/text_document/prepare_rename.rb index 4a37410dd..2f742047a 100644 --- a/lib/solargraph/language_server/message/text_document/prepare_rename.rb +++ b/lib/solargraph/language_server/message/text_document/prepare_rename.rb @@ -1,11 +1,18 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class PrepareRename < Base - def process - line = params['position']['line'] - col = params['position']['character'] - set_result host.sources.find(params['textDocument']['uri']).cursor_at(Solargraph::Position.new(line, col)).range.to_hash +module Solargraph + module LanguageServer + module Message + module TextDocument + class PrepareRename < Base + def process + line = params['position']['line'] + col = params['position']['character'] + set_result host.sources.find(params['textDocument']['uri']).cursor_at(Solargraph::Position.new(line, + col)).range.to_hash + end + end + end end end end diff --git a/lib/solargraph/language_server/message/text_document/references.rb b/lib/solargraph/language_server/message/text_document/references.rb index 2de266214..6a894ecd9 100644 --- a/lib/solargraph/language_server/message/text_document/references.rb +++ b/lib/solargraph/language_server/message/text_document/references.rb @@ -1,16 +1,23 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class References < Base - def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], params['position']['character']) - result = locs.map do |loc| - { - uri: file_to_uri(loc.filename), - range: loc.range.to_hash - } +module Solargraph + module LanguageServer + module Message + module TextDocument + class References < Base + def process + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character']) + result = locs.map do |loc| + { + uri: file_to_uri(loc.filename), + range: loc.range.to_hash + } + end + set_result result + end + end end - set_result result end end end diff --git a/lib/solargraph/language_server/message/text_document/rename.rb b/lib/solargraph/language_server/message/text_document/rename.rb index 997e9595b..b0aff3c8a 100644 --- a/lib/solargraph/language_server/message/text_document/rename.rb +++ b/lib/solargraph/language_server/message/text_document/rename.rb @@ -1,19 +1,26 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class Rename < Base - def process - locs = host.references_from(params['textDocument']['uri'], params['position']['line'], params['position']['character'], strip: true) - changes = {} - locs.each do |loc| - uri = file_to_uri(loc.filename) - changes[uri] ||= [] - changes[uri].push({ - range: loc.range.to_hash, - newText: params['newName'] - }) +module Solargraph + module LanguageServer + module Message + module TextDocument + class Rename < Base + def process + locs = host.references_from(params['textDocument']['uri'], params['position']['line'], + params['position']['character'], strip: true) + changes = {} + locs.each do |loc| + uri = file_to_uri(loc.filename) + changes[uri] ||= [] + changes[uri].push({ + range: loc.range.to_hash, + newText: params['newName'] + }) + end + set_result changes: changes + end + end end - set_result changes: changes end end end diff --git a/lib/solargraph/language_server/message/text_document/signature_help.rb b/lib/solargraph/language_server/message/text_document/signature_help.rb index a56b56edd..675daaebe 100644 --- a/lib/solargraph/language_server/message/text_document/signature_help.rb +++ b/lib/solargraph/language_server/message/text_document/signature_help.rb @@ -10,8 +10,8 @@ def process col = params['position']['character'] suggestions = host.signatures_at(params['textDocument']['uri'], line, col) set_result({ - signatures: suggestions.flat_map { |pin| pin.signature_help } - }) + signatures: suggestions.flat_map(&:signature_help) + }) rescue FileNotFoundError => e Logging.logger.warn "[#{e.class}] #{e.message}" # @sg-ignore Need to add nil check here diff --git a/lib/solargraph/language_server/message/text_document/type_definition.rb b/lib/solargraph/language_server/message/text_document/type_definition.rb index 3a2431659..1035869c9 100644 --- a/lib/solargraph/language_server/message/text_document/type_definition.rb +++ b/lib/solargraph/language_server/message/text_document/type_definition.rb @@ -1,26 +1,32 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::TextDocument - class TypeDefinition < Base - def process - @line = params['position']['line'] - @column = params['position']['character'] - set_result(code_location || []) - end +module Solargraph + module LanguageServer + module Message + module TextDocument + class TypeDefinition < Base + def process + @line = params['position']['line'] + @column = params['position']['character'] + set_result(code_location || []) + end - private + private - # @return [Array, nil] - def code_location - suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) - # @sg-ignore Need to add nil check here - return nil if suggestions.empty? - # @sg-ignore Need to add nil check here - suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| - { - uri: file_to_uri(pin.best_location.filename), - range: pin.best_location.range.to_hash - } + # @return [Array, nil] + def code_location + suggestions = host.type_definitions_at(params['textDocument']['uri'], @line, @column) + # @sg-ignore Need to add nil check here + return nil if suggestions.empty? + # @sg-ignore Need to add nil check here + suggestions.reject { |pin| pin.best_location.nil? || pin.best_location.filename.nil? }.map do |pin| + { + uri: file_to_uri(pin.best_location.filename), + range: pin.best_location.range.to_hash + } + end + end + end end end end diff --git a/lib/solargraph/language_server/message/workspace/did_change_configuration.rb b/lib/solargraph/language_server/message/workspace/did_change_configuration.rb index 5a15ab0a5..2c9bd8d1c 100644 --- a/lib/solargraph/language_server/message/workspace/did_change_configuration.rb +++ b/lib/solargraph/language_server/message/workspace/did_change_configuration.rb @@ -1,35 +1,41 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::Workspace - class DidChangeConfiguration < Solargraph::LanguageServer::Message::Base - def process - return unless params['settings'] - update = params['settings']['solargraph'] - host.configure update - register_from_options - end +module Solargraph + module LanguageServer + module Message + module Workspace + class DidChangeConfiguration < Solargraph::LanguageServer::Message::Base + def process + return unless params['settings'] + update = params['settings']['solargraph'] + host.configure update + register_from_options + end - private + private - # @return [void] - def register_from_options - Solargraph.logger.debug "Registering capabilities from options: #{host.options.inspect}" - # @type [Array] - y = [] - # @type [Array] - n = [] - (host.options['completion'] ? y : n).push('textDocument/completion') - (host.options['hover'] ? y : n).push('textDocument/hover', 'textDocument/signatureHelp') - (host.options['autoformat'] ? y : n).push('textDocument/onTypeFormatting') - (host.options['formatting'] ? y : n).push('textDocument/formatting') - (host.options['symbols'] ? y : n).push('textDocument/documentSymbol', 'workspace/symbol') - (host.options['definitions'] ? y : n).push('textDocument/definition') - (host.options['typeDefinitions'] ? y : n).push('textDocument/typeDefinition') - (host.options['references'] ? y : n).push('textDocument/references') - (host.options['folding'] ? y : n).push('textDocument/folding') - (host.options['highlights'] ? y : n).push('textDocument/documentHighlight') - host.register_capabilities y - host.unregister_capabilities n + # @return [void] + def register_from_options + Solargraph.logger.debug "Registering capabilities from options: #{host.options.inspect}" + # @type [Array] + y = [] + # @type [Array] + n = [] + (host.options['completion'] ? y : n).push('textDocument/completion') + (host.options['hover'] ? y : n).push('textDocument/hover', 'textDocument/signatureHelp') + (host.options['autoformat'] ? y : n).push('textDocument/onTypeFormatting') + (host.options['formatting'] ? y : n).push('textDocument/formatting') + (host.options['symbols'] ? y : n).push('textDocument/documentSymbol', 'workspace/symbol') + (host.options['definitions'] ? y : n).push('textDocument/definition') + (host.options['typeDefinitions'] ? y : n).push('textDocument/typeDefinition') + (host.options['references'] ? y : n).push('textDocument/references') + (host.options['folding'] ? y : n).push('textDocument/folding') + (host.options['highlights'] ? y : n).push('textDocument/documentHighlight') + host.register_capabilities y + host.unregister_capabilities n + end + end + end end end end diff --git a/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb b/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb index 32b2c1c50..d44c24097 100644 --- a/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb +++ b/lib/solargraph/language_server/message/workspace/did_change_watched_files.rb @@ -1,40 +1,48 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::Workspace - class DidChangeWatchedFiles < Solargraph::LanguageServer::Message::Base - CREATED = 1 - CHANGED = 2 - DELETED = 3 +module Solargraph + module LanguageServer + module Message + module Workspace + class DidChangeWatchedFiles < Solargraph::LanguageServer::Message::Base + CREATED = 1 + CHANGED = 2 + DELETED = 3 - include Solargraph::LanguageServer::UriHelpers + include Solargraph::LanguageServer::UriHelpers - def process - need_catalog = false - to_create = [] - to_delete = [] + def process + need_catalog = false + to_create = [] + to_delete = [] - # @param change [Hash] - params['changes'].each do |change| - if change['type'] == CREATED - to_create << change['uri'] - need_catalog = true - elsif change['type'] == CHANGED - next if host.open?(change['uri']) - to_create << change['uri'] - need_catalog = true - elsif change['type'] == DELETED - to_delete << change['uri'] - need_catalog = true - else - set_error Solargraph::LanguageServer::ErrorCodes::INVALID_PARAMS, "Unknown change type ##{change['type']} for #{uri_to_file(change['uri'])}" - end - end + # @param change [Hash] + params['changes'].each do |change| + case change['type'] + when CREATED + to_create << change['uri'] + need_catalog = true + when CHANGED + next if host.open?(change['uri']) + to_create << change['uri'] + need_catalog = true + when DELETED + to_delete << change['uri'] + need_catalog = true + else + set_error Solargraph::LanguageServer::ErrorCodes::INVALID_PARAMS, + "Unknown change type ##{change['type']} for #{uri_to_file(change['uri'])}" + end + end - host.create *to_create - host.delete *to_delete + host.create(*to_create) + host.delete(*to_delete) - # Force host to catalog libraries after file changes (see castwide/solargraph#139) - host.catalog if need_catalog + # Force host to catalog libraries after file changes (see castwide/solargraph#139) + host.catalog if need_catalog + end + end + end end end end diff --git a/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb b/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb index e1e83fc1e..7ba16b66c 100644 --- a/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb +++ b/lib/solargraph/language_server/message/workspace/did_change_workspace_folders.rb @@ -1,25 +1,31 @@ # frozen_string_literal: true -module Solargraph::LanguageServer::Message::Workspace - class DidChangeWorkspaceFolders < Solargraph::LanguageServer::Message::Base - def process - add_folders - remove_folders - end +module Solargraph + module LanguageServer + module Message + module Workspace + class DidChangeWorkspaceFolders < Solargraph::LanguageServer::Message::Base + def process + add_folders + remove_folders + end - private + private - # @return [void] - def add_folders - return unless params['event'] && params['event']['added'] - host.prepare_folders params['event']['added'] - end + # @return [void] + def add_folders + return unless params['event'] && params['event']['added'] + host.prepare_folders params['event']['added'] + end - # @return [void] - def remove_folders - return unless params['event'] && params['event']['removed'] - params['event']['removed'].each do |folder| - host.remove_folders params['event']['removed'] + # @return [void] + def remove_folders + return unless params['event'] && params['event']['removed'] + params['event']['removed'].each do |_folder| + host.remove_folders params['event']['removed'] + end + end + end end end end diff --git a/lib/solargraph/language_server/message/workspace/workspace_symbol.rb b/lib/solargraph/language_server/message/workspace/workspace_symbol.rb index fe7e5efa5..8e8c884a7 100644 --- a/lib/solargraph/language_server/message/workspace/workspace_symbol.rb +++ b/lib/solargraph/language_server/message/workspace/workspace_symbol.rb @@ -1,25 +1,33 @@ # frozen_string_literal: true -class Solargraph::LanguageServer::Message::Workspace::WorkspaceSymbol < Solargraph::LanguageServer::Message::Base - include Solargraph::LanguageServer::UriHelpers +module Solargraph + module LanguageServer + module Message + module Workspace + class WorkspaceSymbol < Solargraph::LanguageServer::Message::Base + include Solargraph::LanguageServer::UriHelpers - def process - pins = host.query_symbols(params['query']) - info = pins.map do |pin| - # @sg-ignore Need to add nil check here - uri = file_to_uri(pin.best_location.filename) - { - name: pin.path, - containerName: pin.namespace, - kind: pin.symbol_kind, - location: { - uri: uri, - # @sg-ignore Need to add nil check here - range: pin.best_location.range.to_hash - }, - deprecated: pin.deprecated? - } + def process + pins = host.query_symbols(params['query']) + info = pins.map do |pin| + # @sg-ignore Need to add nil check here + uri = file_to_uri(pin.best_location.filename) + { + name: pin.path, + containerName: pin.namespace, + kind: pin.symbol_kind, + location: { + uri: uri, + # @sg-ignore Need to add nil check here + range: pin.best_location.range.to_hash + }, + deprecated: pin.deprecated? + } + end + set_result info + end + end + end end - set_result info end end diff --git a/lib/solargraph/language_server/request.rb b/lib/solargraph/language_server/request.rb index 2cc874613..2a2a29221 100644 --- a/lib/solargraph/language_server/request.rb +++ b/lib/solargraph/language_server/request.rb @@ -4,18 +4,20 @@ module Solargraph module LanguageServer class Request # @param id [Integer] - # @param &block The block that processes the client's response + # @param block [Proc] The block that processes the client's response def initialize id, &block @id = id @block = block end + # @sg-ignore Solargraph::LanguageServer::Request#process return + # type could not be inferred # @param result [Object] # @generic T # @yieldreturn [generic] # @return [generic, nil] def process result - @block.call(result) unless @block.nil? + @block&.call(result) end # @return [void] diff --git a/lib/solargraph/language_server/transport/data_reader.rb b/lib/solargraph/language_server/transport/data_reader.rb index 3fc3ed311..5145e41e6 100644 --- a/lib/solargraph/language_server/transport/data_reader.rb +++ b/lib/solargraph/language_server/transport/data_reader.rb @@ -33,8 +33,8 @@ def receive data @buffer.concat char if @in_header prepare_to_parse_message if @buffer.end_with?("\r\n\r\n") - else - parse_message_from_buffer if @buffer.bytesize == @content_length + elsif @buffer.bytesize == @content_length + parse_message_from_buffer end end end @@ -56,17 +56,15 @@ def prepare_to_parse_message # @return [void] def parse_message_from_buffer - begin - msg = JSON.parse(@buffer) - @message_handler.call msg unless @message_handler.nil? - rescue JSON::ParserError => e - Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}" - Solargraph::Logging.logger.debug "Buffer: #{@buffer}" - ensure - @buffer.clear - @in_header = true - @content_length = 0 - end + msg = JSON.parse(@buffer) + @message_handler&.call msg + rescue JSON::ParserError => e + Solargraph::Logging.logger.warn "Failed to parse request: #{e.message}" + Solargraph::Logging.logger.debug "Buffer: #{@buffer}" + ensure + @buffer.clear + @in_header = true + @content_length = 0 end end end diff --git a/lib/solargraph/language_server/uri_helpers.rb b/lib/solargraph/language_server/uri_helpers.rb index c7e55afb8..fcf2e0dcb 100644 --- a/lib/solargraph/language_server/uri_helpers.rb +++ b/lib/solargraph/language_server/uri_helpers.rb @@ -14,7 +14,7 @@ module UriHelpers # @param uri [String] # @return [String] def uri_to_file uri - decode(uri).sub(/^file\:(?:\/\/)?/, '').sub(/^\/([a-z]\:)/i, '\1') + decode(uri).sub(%r{^file:(?://)?}, '').sub(%r{^/([a-z]:)}i, '\1') end # Convert a file path to a URI. @@ -22,7 +22,7 @@ def uri_to_file uri # @param file [String] # @return [String] def file_to_uri file - "file://#{encode(file.gsub(/^([a-z]\:)/i, '/\1'))}" + "file://#{encode(file.gsub(/^([a-z]:)/i, '/\1'))}" end # Encode text to be used as a URI path component in LSP. diff --git a/lib/solargraph/library.rb b/lib/solargraph/library.rb index 019d8d91c..8cf806275 100644 --- a/lib/solargraph/library.rb +++ b/lib/solargraph/library.rb @@ -127,8 +127,8 @@ def create filename, text # @return [Boolean] True if at least one file was added to the workspace. def create_from_disk *filenames sources = filenames - .reject { |filename| File.directory?(filename) || !File.exist?(filename) } - .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) } + .reject { |filename| File.directory?(filename) || !File.exist?(filename) } + .map { |filename| Solargraph::Source.load_string(File.read(filename), filename) } result = workspace.merge(*sources) sources.each { |source| maybe_map source } result @@ -195,10 +195,10 @@ def definitions_at filename, line, column offset = Solargraph::Position.to_offset(source.code, Solargraph::Position.new(line, column)) # @sg-ignore Need to add nil check here # @type [MatchData, nil] - lft = source.code[0..offset-1].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i) + lft = source.code[0..(offset - 1)].match(/\[[a-z0-9_:<, ]*?([a-z0-9_:]*)\z/i) # @sg-ignore Need to add nil check here # @type [MatchData, nil] - rgt = source.code[offset..-1].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) + rgt = source.code[offset..].match(/^([a-z0-9_]*)(:[a-z0-9_:]*)?[\]>, ]/i) if lft && rgt # @sg-ignore Need to add nil check here tag = (lft[1] + rgt[1]).sub(/:+$/, '') @@ -264,10 +264,10 @@ def references_from filename, line, column, strip: false, only: false return [] unless pin result = [] files = if only - [api_map.source_map(filename)] - else - (workspace.sources + (@current ? [@current] : [])) - end + [api_map.source_map(filename)] + else + (workspace.sources + (@current ? [@current] : [])) + end files.uniq(&:filename).each do |source| found = source.references(pin.name) found.select! do |loc| @@ -291,7 +291,7 @@ def references_from filename, line, column, strip: false, only: false end end # HACK: for language clients that exclude special characters from the start of variable names - if strip && match = cursor.word.match(/^[^a-z0-9_]+/i) + if strip && (match = cursor.word.match(/^[^a-z0-9_]+/i)) found.map! do |loc| Solargraph::Location.new(loc.filename, # @sg-ignore flow sensitive typing needs to handle if foo = bar @@ -329,8 +329,8 @@ def locate_ref location end workspace.require_paths.each do |path| full = File.join path, pin.name - return_if_match.(full) - return_if_match.(full << ".rb") + return_if_match.call(full) + return_if_match.call(full << '.rb') end nil rescue FileNotFoundError @@ -543,15 +543,15 @@ def source_map_external_require_hash # @return [void] def find_external_requires source_map # @type [Set] - new_set = source_map.requires.map(&:name).to_set + new_set = source_map.requires.to_set(&:name) # return if new_set == source_map_external_require_hash[source_map.filename] _filenames = nil - filenames = ->{ _filenames ||= workspace.filenames.to_set } + filenames = -> { _filenames ||= workspace.filenames.to_set } # @sg-ignore Need to add nil check here source_map_external_require_hash[source_map.filename] = new_set.reject do |path| workspace.require_paths.any? do |base| full = File.join(base, path) - filenames[].include?(full) or filenames[].include?(full << ".rb") + filenames[].include?(full) or filenames[].include?(full << '.rb') end end @external_requires = nil @@ -585,12 +585,9 @@ def read filename # @param error [FileNotFoundError] # @return [nil] def handle_file_not_found filename, error - if workspace.source(filename) - Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap" - nil - else - raise error - end + raise error unless workspace.source(filename) + Solargraph.logger.debug "#{filename} is not cataloged in the ApiMap" + nil end # @param source [Source, nil] @@ -679,16 +676,14 @@ def report_cache_progress gem_name, pending finished = @total - pending # @sg-ignore flow sensitive typing needs better handling of ||= on lvars pct = if @total.zero? - 0 - else - # @sg-ignore flow sensitive typing needs better handling of ||= on lvars - ((finished.to_f / @total.to_f) * 100).to_i - end - message = "#{gem_name}#{pending > 0 ? " (+#{pending})" : ''}" + 0 + else + # @sg-ignore flow sensitive typing needs better handling of ||= on lvars + ((finished.to_f / @total) * 100).to_i + end + message = "#{gem_name}#{" (+#{pending})" if pending.positive?}" # " - if @cache_progress - @cache_progress.report(message, pct) - else + unless @cache_progress @cache_progress = LanguageServer::Progress.new('Caching gem') # If we don't send both a begin and a report, the progress notification # might get stuck in the status bar forever @@ -696,9 +691,9 @@ def report_cache_progress gem_name, pending @cache_progress.begin(message, pct) changed notify_observers @cache_progress - # @sg-ignore flow sensitive typing should be able to handle redefinition - @cache_progress.report(message, pct) end + # @sg-ignore flow sensitive typing should be able to handle redefinition + @cache_progress.report(message, pct) changed notify_observers @cache_progress end @@ -713,11 +708,11 @@ def end_cache_progress # @return [void] def sync_catalog - return if @sync_count == 0 + return if @sync_count.zero? mutex.synchronize do logger.info "Cataloging #{workspace.directory.empty? ? 'generic workspace' : workspace.directory}" - source_map_hash.values.each { |map| find_external_requires(map) } + source_map_hash.each_value { |map| find_external_requires(map) } api_map.catalog bench logger.info "Catalog complete (#{api_map.source_maps.length} files, #{api_map.pins.length} pins)" logger.info "#{api_map.uncached_gemspecs.length} uncached gemspecs" diff --git a/lib/solargraph/location.rb b/lib/solargraph/location.rb index a7b15e42f..2317c3cb4 100644 --- a/lib/solargraph/location.rb +++ b/lib/solargraph/location.rb @@ -17,18 +17,14 @@ class Location # @param filename [String, nil] # @param range [Solargraph::Range] def initialize filename, range - raise "Use nil to represent no-file" if filename&.empty? + raise 'Use nil to represent no-file' if filename&.empty? @filename = filename @range = range end - protected def equality_fields - [filename, range] - end - # @param other [self] - def <=>(other) + def <=> other return nil unless other.is_a?(Location) if filename == other.filename range <=> other.range @@ -46,10 +42,6 @@ def contain? location range.contain?(location.range.start) && range.contain?(location.range.ending) && filename == location.filename end - def inspect - "<#{self.class.name}: filename=#{filename}, range=#{range.inspect}>" - end - def to_s inspect end @@ -64,14 +56,14 @@ def to_hash # @param node [Parser::AST::Node, nil] # @return [Location, nil] - def self.from_node(node) + def self.from_node node return nil if node.nil? || node.loc.nil? filename = node.loc.expression.source_buffer.name # @sg-ignore flow sensitive typing needs to create separate ranges for postfix if filename = nil if filename.empty? range = Range.from_node(node) # @sg-ignore Need to add nil check here - self.new(filename, range) + new(filename, range) end # @param other [BasicObject] @@ -83,5 +75,11 @@ def == other def inspect "#<#{self.class} #{filename}, #{range.inspect}>" end + + protected + + def equality_fields + [filename, range] + end end end diff --git a/lib/solargraph/logging.rb b/lib/solargraph/logging.rb index 30bdc06c2..d472438e2 100644 --- a/lib/solargraph/logging.rb +++ b/lib/solargraph/logging.rb @@ -10,7 +10,7 @@ module Logging 'warn' => Logger::WARN, 'info' => Logger::INFO, 'debug' => Logger::DEBUG - } + }.freeze configured_level = ENV.fetch('SOLARGRAPH_LOG', nil) level = if LOG_LEVELS.keys.include?(configured_level) LOG_LEVELS.fetch(configured_level) @@ -21,9 +21,9 @@ module Logging end DEFAULT_LOG_LEVEL end - @@logger = Logger.new(STDERR, level: level) + @@logger = Logger.new($stderr, level: level) # @sg-ignore Fix cvar issue - @@logger.formatter = proc do |severity, datetime, progname, msg| + @@logger.formatter = proc do |severity, _datetime, _progname, msg| "[#{severity}] #{msg}\n" end @@ -45,7 +45,7 @@ def logger @@logger else new_log_level = LOG_LEVELS[log_level.to_s] - logger = Logger.new(STDERR, level: new_log_level) + logger = Logger.new($stderr, level: new_log_level) # @sg-ignore Wrong argument type for Logger#formatter=: arg_0 # expected nil, received Logger::_Formatter, nil diff --git a/lib/solargraph/page.rb b/lib/solargraph/page.rb index 12782da90..207b84665 100644 --- a/lib/solargraph/page.rb +++ b/lib/solargraph/page.rb @@ -49,7 +49,7 @@ def ruby_to_html code # @param directory [String] def initialize directory = VIEWS_PATH - directory = VIEWS_PATH if directory.nil? or !File.directory?(directory) + directory = VIEWS_PATH if directory.nil? || !File.directory?(directory) directories = [directory] directories.push VIEWS_PATH if directory != VIEWS_PATH # @type [Proc] diff --git a/lib/solargraph/parser.rb b/lib/solargraph/parser.rb index 1f02befe7..7479f4fcc 100644 --- a/lib/solargraph/parser.rb +++ b/lib/solargraph/parser.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Parser autoload :CommentRipper, 'solargraph/parser/comment_ripper' diff --git a/lib/solargraph/parser/comment_ripper.rb b/lib/solargraph/parser/comment_ripper.rb index 8bfe166f5..89d4a2c91 100644 --- a/lib/solargraph/parser/comment_ripper.rb +++ b/lib/solargraph/parser/comment_ripper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'ripper' module Solargraph @@ -26,19 +28,25 @@ def on_comment *args # @sg-ignore Need to add nil check here if @buffer_lines[result[2][0]][0..result[2][1]].strip =~ /^#/ chomped = result[1].chomp - if result[2][0] == 0 && chomped.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '').match(/^#\s*frozen_string_literal:/) + if result[2][0].zero? && chomped.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, + replace: '').match(/^#\s*frozen_string_literal:/) chomped = '#' end - @comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0], result[2][1], result[2][0], result[2][1] + chomped.length), chomped) + @comments[result[2][0]] = + Snippet.new(Range.from_to(result[2][0], result[2][1], result[2][0], result[2][1] + chomped.length), chomped) end result end # @param result [Array(Symbol, String, Array([Integer, nil], [Integer, nil]))] # @return [void] - def create_snippet(result) + def create_snippet result chomped = result[1].chomp - @comments[result[2][0]] = Snippet.new(Range.from_to(result[2][0] || 0, result[2][1] || 0, result[2][0] || 0, (result[2][1] || 0) + chomped.length), chomped) + @comments[result[2][0]] = + Snippet.new( + Range.from_to(result[2][0] || 0, result[2][1] || 0, result[2][0] || 0, + (result[2][1] || 0) + chomped.length), chomped + ) end # @sg-ignore @override is adding, not overriding diff --git a/lib/solargraph/parser/flow_sensitive_typing.rb b/lib/solargraph/parser/flow_sensitive_typing.rb index e73a46cb2..32e4866d1 100644 --- a/lib/solargraph/parser/flow_sensitive_typing.rb +++ b/lib/solargraph/parser/flow_sensitive_typing.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Parser class FlowSensitiveTyping @@ -7,7 +9,7 @@ class FlowSensitiveTyping # @param ivars [Array] # @param enclosing_breakable_pin [Solargraph::Pin::Breakable, nil] # @param enclosing_compound_statement_pin [Solargraph::Pin::CompoundStatement, nil] - def initialize(locals, ivars, enclosing_breakable_pin, enclosing_compound_statement_pin) + def initialize locals, ivars, enclosing_breakable_pin, enclosing_compound_statement_pin @locals = locals @ivars = ivars @enclosing_breakable_pin = enclosing_breakable_pin @@ -19,7 +21,7 @@ def initialize(locals, ivars, enclosing_breakable_pin, enclosing_compound_statem # @param false_ranges [Array] # # @return [void] - def process_and(and_node, true_ranges = [], false_ranges = []) + def process_and and_node, true_ranges = [], false_ranges = [] return unless and_node.type == :and # @type [Parser::AST::Node] @@ -45,7 +47,7 @@ def process_and(and_node, true_ranges = [], false_ranges = []) # @param false_ranges [Array] # # @return [void] - def process_or(or_node, true_ranges = [], false_ranges = []) + def process_or or_node, true_ranges = [], false_ranges = [] return unless or_node.type == :or # @type [Parser::AST::Node] @@ -74,7 +76,7 @@ def process_or(or_node, true_ranges = [], false_ranges = []) # @param false_presences [Array] # # @return [void] - def process_calls(node, true_presences, false_presences) + def process_calls node, true_presences, false_presences return unless node.type == :send process_isa(node, true_presences, false_presences) @@ -87,7 +89,7 @@ def process_calls(node, true_presences, false_presences) # @param false_ranges [Array] # # @return [void] - def process_if(if_node, true_ranges = [], false_ranges = []) + def process_if if_node, true_ranges = [], false_ranges = [] return if if_node.type != :if # @@ -111,13 +113,9 @@ def process_if(if_node, true_ranges = [], false_ranges = []) rest_of_breakable_body = Range.new(get_node_end_position(if_node), get_node_end_position(enclosing_breakable_pin.node)) - if always_breaks?(then_clause) - false_ranges << rest_of_breakable_body - end + false_ranges << rest_of_breakable_body if always_breaks?(then_clause) - if always_breaks?(else_clause) - true_ranges << rest_of_breakable_body - end + true_ranges << rest_of_breakable_body if always_breaks?(else_clause) end unless enclosing_compound_statement_pin.node.nil? @@ -129,13 +127,9 @@ def process_if(if_node, true_ranges = [], false_ranges = []) # statement, we can assume things about the rest of the # compound statement # - if always_leaves_compound_statement?(then_clause) - false_ranges << rest_of_returnable_body - end + false_ranges << rest_of_returnable_body if always_leaves_compound_statement?(then_clause) - if always_leaves_compound_statement?(else_clause) - true_ranges << rest_of_returnable_body - end + true_ranges << rest_of_returnable_body if always_leaves_compound_statement?(else_clause) end unless then_clause.nil? @@ -166,7 +160,7 @@ def process_if(if_node, true_ranges = [], false_ranges = []) # @param false_ranges [Array] # # @return [void] - def process_while(while_node, true_ranges = [], false_ranges = []) + def process_while while_node, true_ranges = [], false_ranges = [] return if while_node.type != :while # @@ -210,7 +204,7 @@ class << self # @param downcast_not_type [ComplexType, nil] # # @return [void] - def add_downcast_var(pin, presence:, downcast_type:, downcast_not_type:) + def add_downcast_var pin, presence:, downcast_type:, downcast_not_type: new_pin = pin.downcast(exclude_return_type: downcast_not_type, intersection_return_type: downcast_type, source: :flow_sensitive_typing, @@ -228,7 +222,7 @@ def add_downcast_var(pin, presence:, downcast_type:, downcast_not_type:) # @param presences [Array] # # @return [void] - def process_facts(facts_by_pin, presences) + def process_facts facts_by_pin, presences # # Add specialized vars for the rest of the block # @@ -251,7 +245,7 @@ def process_facts(facts_by_pin, presences) # @param false_ranges [Array] # # @return [void] - def process_expression(expression_node, true_ranges, false_ranges) + def process_expression expression_node, true_ranges, false_ranges process_calls(expression_node, true_ranges, false_ranges) process_and(expression_node, true_ranges, false_ranges) process_or(expression_node, true_ranges, false_ranges) @@ -263,7 +257,7 @@ def process_expression(expression_node, true_ranges, false_ranges) # @return [Array(String, String), nil] Tuple of rgument to # function, then receiver of function if it's a variable, # otherwise nil if no simple variable receiver - def parse_call(call_node, method_name) + def parse_call call_node, method_name return unless call_node&.type == :send && call_node.children[1] == method_name # Check if conditional node follows this pattern: # s(:send, @@ -282,7 +276,7 @@ def parse_call(call_node, method_name) # or like this: # (lvar :repr) # @sg-ignore Need to look at Tuple#include? handling - variable_name = call_receiver.children[0].to_s if [:lvar, :ivar].include?(call_receiver&.type) + variable_name = call_receiver.children[0].to_s if %i[lvar ivar].include?(call_receiver&.type) return unless variable_name [call_arg, variable_name] @@ -290,7 +284,7 @@ def parse_call(call_node, method_name) # @param isa_node [Parser::AST::Node] # @return [Array(String, String), nil] - def parse_isa(isa_node) + def parse_isa isa_node call_type_name, variable_name = parse_call(isa_node, :is_a?) return unless call_type_name @@ -304,7 +298,7 @@ def parse_isa(isa_node) # @sg-ignore Solargraph::Parser::FlowSensitiveTyping#find_var # return type could not be inferred # @return [Solargraph::Pin::LocalVariable, Solargraph::Pin::InstanceVariable, nil] - def find_var(variable_name, position) + def find_var variable_name, position if variable_name.start_with?('@') # @sg-ignore flow sensitive typing needs to handle attrs ivars.find { |ivar| ivar.name == variable_name && (!ivar.presence || ivar.presence.include?(position)) } @@ -319,7 +313,7 @@ def find_var(variable_name, position) # @param false_presences [Array] # # @return [void] - def process_isa(isa_node, true_presences, false_presences) + def process_isa isa_node, true_presences, false_presences isa_type_name, variable_name = parse_isa(isa_node) return if variable_name.nil? || variable_name.empty? # @sg-ignore Need to add nil check here @@ -343,7 +337,7 @@ def process_isa(isa_node, true_presences, false_presences) # @param nilp_node [Parser::AST::Node] # @return [Array(String, String), nil] - def parse_nilp(nilp_node) + def parse_nilp nilp_node parse_call(nilp_node, :nil?) end @@ -352,7 +346,7 @@ def parse_nilp(nilp_node) # @param false_presences [Array] # # @return [void] - def process_nilp(nilp_node, true_presences, false_presences) + def process_nilp nilp_node, true_presences, false_presences nilp_arg, variable_name = parse_nilp(nilp_node) return if variable_name.nil? || variable_name.empty? # if .nil? got an argument, move on, this isn't the situation @@ -380,7 +374,7 @@ def process_nilp(nilp_node, true_presences, false_presences) # @param bang_node [Parser::AST::Node] # @return [Array(String, String), nil] - def parse_bang(bang_node) + def parse_bang bang_node parse_call(bang_node, :!) end @@ -389,7 +383,7 @@ def parse_bang(bang_node) # @param false_presences [Array] # # @return [void] - def process_bang(bang_node, true_presences, false_presences) + def process_bang bang_node, true_presences, false_presences # pry(main)> require 'parser/current'; Parser::CurrentRuby.parse("!2") # => s(:send, # s(:int, 2), :!) @@ -405,7 +399,7 @@ def process_bang(bang_node, true_presences, false_presences) # @param var_node [Parser::AST::Node] # # @return [String, nil] Variable name referenced - def parse_variable(var_node) + def parse_variable var_node return if var_node.children.length != 1 var_node.children[0]&.to_s @@ -415,8 +409,8 @@ def parse_variable(var_node) # @param node [Parser::AST::Node] # @param true_presences [Array] # @param false_presences [Array] - def process_variable(node, true_presences, false_presences) - return unless [:lvar, :ivar, :cvar, :gvar].include?(node.type) + def process_variable node, true_presences, false_presences + return unless %i[lvar ivar cvar gvar].include?(node.type) variable_name = parse_variable(node) return if variable_name.nil? @@ -443,7 +437,7 @@ def process_variable(node, true_presences, false_presences) # @param node [Parser::AST::Node] # # @return [String, nil] - def type_name(node) + def type_name node # e.g., # s(:const, nil, :Baz) return unless node&.type == :const @@ -462,22 +456,18 @@ def type_name(node) # @param clause_node [Parser::AST::Node, nil] # @sg-ignore need boolish support for ? methods - def always_breaks?(clause_node) + def always_breaks? clause_node clause_node&.type == :break end # @param clause_node [Parser::AST::Node, nil] - def always_leaves_compound_statement?(clause_node) + def always_leaves_compound_statement? clause_node # https://docs.ruby-lang.org/en/2.2.0/keywords_rdoc.html # @sg-ignore Need to look at Tuple#include? handling - [:return, :raise, :next, :redo, :retry].include?(clause_node&.type) + %i[return raise next redo retry].include?(clause_node&.type) end - attr_reader :locals - - attr_reader :ivars - - attr_reader :enclosing_breakable_pin, :enclosing_compound_statement_pin + attr_reader :locals, :ivars, :enclosing_breakable_pin, :enclosing_compound_statement_pin end end end diff --git a/lib/solargraph/parser/node_processor.rb b/lib/solargraph/parser/node_processor.rb index a43fb326c..d3579df80 100644 --- a/lib/solargraph/parser/node_processor.rb +++ b/lib/solargraph/parser/node_processor.rb @@ -43,7 +43,7 @@ def self.process node, region = Region.new, pins = [], locals = [], ivars = [] pins.push Pin::Namespace.new( location: region.source.location, name: '', - source: :parser, + source: :parser ) end return [pins, locals, ivars] unless Parser.is_ast_node?(node) diff --git a/lib/solargraph/parser/node_processor/base.rb b/lib/solargraph/parser/node_processor/base.rb index e11078e07..48fd070f6 100644 --- a/lib/solargraph/parser/node_processor/base.rb +++ b/lib/solargraph/parser/node_processor/base.rb @@ -58,13 +58,13 @@ def position # @sg-ignore downcast output of Enumerable#select # @return [Solargraph::Pin::Breakable, nil] def enclosing_breakable_pin - pins.select{|pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position)}.last + pins.select { |pin| pin.is_a?(Pin::Breakable) && pin.location&.range&.contain?(position) }.last end # @todo downcast output of Enumerable#select # @return [Solargraph::Pin::CompoundStatement, nil] def enclosing_compound_statement_pin - pins.select{|pin| pin.is_a?(Pin::CompoundStatement) && pin.location&.range&.contain?(position)}.last + pins.select { |pin| pin.is_a?(Pin::CompoundStatement) && pin.location&.range&.contain?(position) }.last end # @param subregion [Region] @@ -80,14 +80,14 @@ def process_children subregion = region # @param node [Parser::AST::Node] # @return [Solargraph::Location] - def get_node_location(node) + def get_node_location node range = Parser.node_range(node) Location.new(region.filename, range) end # @param node [Parser::AST::Node] # @return [String, nil] - def comments_for(node) + def comments_for node region.source.comments_for(node) end diff --git a/lib/solargraph/parser/parser_gem.rb b/lib/solargraph/parser/parser_gem.rb index 857e17768..3aca3804f 100644 --- a/lib/solargraph/parser/parser_gem.rb +++ b/lib/solargraph/parser/parser_gem.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Parser module ParserGem diff --git a/lib/solargraph/parser/parser_gem/class_methods.rb b/lib/solargraph/parser/parser_gem/class_methods.rb index 3f70d14df..dfaf7925a 100644 --- a/lib/solargraph/parser/parser_gem/class_methods.rb +++ b/lib/solargraph/parser/parser_gem/class_methods.rb @@ -54,7 +54,7 @@ def map source # @param name [String] # @return [Array] def references source, name - if name.end_with?("=") + if name.end_with?('=') reg = /#{Regexp.escape name[0..-2]}\s*=/ # @param code [String] # @param offset [Integer] @@ -98,17 +98,17 @@ def inner_node_references name, top # @return [Source::Chain] def chain *args - NodeChainer.chain *args + NodeChainer.chain(*args) end # @return [Source::Chain] def chain_string *args - NodeChainer.load_string *args + NodeChainer.load_string(*args) end # @return [Array(Array, Array)] def process_node *args - Solargraph::Parser::NodeProcessor.process *args + Solargraph::Parser::NodeProcessor.process(*args) end # @param node [Parser::AST::Node, nil] diff --git a/lib/solargraph/parser/parser_gem/flawed_builder.rb b/lib/solargraph/parser/parser_gem/flawed_builder.rb index acf665e16..341042f22 100644 --- a/lib/solargraph/parser/parser_gem/flawed_builder.rb +++ b/lib/solargraph/parser/parser_gem/flawed_builder.rb @@ -10,7 +10,7 @@ class FlawedBuilder < ::Parser::Builders::Default # @param token [::Parser::AST::Node] # @return [String] # @sg-ignore - def string_value(token) + def string_value token value(token) end end diff --git a/lib/solargraph/parser/parser_gem/node_chainer.rb b/lib/solargraph/parser/parser_gem/node_chainer.rb index bc04c2855..be6e287cf 100644 --- a/lib/solargraph/parser/parser_gem/node_chainer.rb +++ b/lib/solargraph/parser/parser_gem/node_chainer.rb @@ -22,7 +22,7 @@ def initialize node, filename = nil, parent = nil # @return [Source::Chain] def chain links = generate_links(@node) - Chain.new(links, @node, (Parser.is_ast_node?(@node) && @node.type == :splat)) + Chain.new(links, @node, Parser.is_ast_node?(@node) && @node.type == :splat) end class << self @@ -39,7 +39,7 @@ def chain node, filename = nil, parent = nil # @param starting_line [Integer] # # @return [Source::Chain] - def load_string(code, filename, starting_line) + def load_string code, filename, starting_line node = Parser.parse(code.sub(/\.$/, ''), filename, starting_line) chain = NodeChainer.new(node).chain chain.links.push(Chain::Link.new) if code.end_with?('.') @@ -63,10 +63,9 @@ def generate_links n result.concat generate_links(n.children[0]) result.push Chain::Call.new(n.children[1].to_s, Location.from_node(n), node_args(n), passed_block(n)) elsif n.children[0].nil? - args = [] # @sg-ignore Need to add nil check here - n.children[2..-1].each do |c| - args.push NodeChainer.chain(c, @filename, n) + n.children[2..].map do |c| + NodeChainer.chain(c, @filename, n) end result.push Chain::Call.new(n.children[1].to_s, Location.from_node(n), node_args(n), passed_block(n)) else @@ -94,13 +93,13 @@ def generate_links n elsif n.type == :const const = unpack_name(n) result.push Chain::Constant.new(const) - elsif [:lvar, :lvasgn].include?(n.type) + elsif %i[lvar lvasgn].include?(n.type) result.push Chain::Call.new(n.children[0].to_s, Location.from_node(n)) - elsif [:ivar, :ivasgn].include?(n.type) + elsif %i[ivar ivasgn].include?(n.type) result.push Chain::InstanceVariable.new(n.children[0].to_s, n, Location.from_node(n)) - elsif [:cvar, :cvasgn].include?(n.type) + elsif %i[cvar cvasgn].include?(n.type) result.push Chain::ClassVariable.new(n.children[0].to_s) - elsif [:gvar, :gvasgn].include?(n.type) + elsif %i[gvar gvasgn].include?(n.type) result.push Chain::GlobalVariable.new(n.children[0].to_s) elsif n.type == :or_asgn # @bar ||= 123 translates to: @@ -113,13 +112,14 @@ def generate_links n or_link = Chain::Or.new([lhs_chain, rhs_chain]) # this is just for a call chain, so we don't need to record the assignment result.push(or_link) - elsif [:class, :module, :def, :defs].include?(n.type) + elsif %i[class module def defs].include?(n.type) # @todo Undefined or what? result.push Chain::UNDEFINED_CALL elsif n.type == :and result.concat generate_links(n.children.last) elsif n.type == :or - result.push Chain::Or.new([NodeChainer.chain(n.children[0], @filename), NodeChainer.chain(n.children[1], @filename, n)]) + result.push Chain::Or.new([NodeChainer.chain(n.children[0], @filename), + NodeChainer.chain(n.children[1], @filename, n)]) elsif n.type == :if then_clause = if n.children[1] NodeChainer.chain(n.children[1], @filename, n) @@ -132,7 +132,7 @@ def generate_links n Source::Chain.new([Source::Chain::Literal.new('nil', nil)], n) end result.push Chain::If.new([then_clause, else_clause]) - elsif [:begin, :kwbegin].include?(n.type) + elsif %i[begin kwbegin].include?(n.type) result.concat generate_links(n.children.last) elsif n.type == :block_pass block_variable_name_node = n.children[0] @@ -140,12 +140,10 @@ def generate_links n # anonymous block forwarding (e.g., "&") # added in Ruby 3.1 - https://bugs.ruby-lang.org/issues/11256 result.push Chain::BlockVariable.new(nil) + elsif block_variable_name_node.type == :sym + result.push Chain::BlockSymbol.new(block_variable_name_node.children[0].to_s) else - if block_variable_name_node.type == :sym - result.push Chain::BlockSymbol.new("#{block_variable_name_node.children[0].to_s}") - else - result.push Chain::BlockVariable.new("&#{block_variable_name_node.children[0].to_s}") - end + result.push Chain::BlockVariable.new("&#{block_variable_name_node.children[0]}") end elsif n.type == :hash result.push Chain::Hash.new('::Hash', n, hash_is_splatted?(n)) @@ -154,7 +152,7 @@ def generate_links n result.push Source::Chain::Array.new(chained_children, n) else lit = infer_literal_node_type(n) - result.push (lit ? Chain::Literal.new(lit, n) : Chain::Link.new) + result.push(lit ? Chain::Literal.new(lit, n) : Chain::Link.new) end result end @@ -163,7 +161,9 @@ def generate_links n def hash_is_splatted? node return false unless Parser.is_ast_node?(node) && node.type == :hash return false unless Parser.is_ast_node?(node.children.last) && node.children.last.type == :kwsplat - return false if Parser.is_ast_node?(node.children.last.children[0]) && node.children.last.children[0].type == :hash + if Parser.is_ast_node?(node.children.last.children[0]) && node.children.last.children[0].type == :hash + return false + end true end @@ -181,7 +181,7 @@ def passed_block node # @return [Array] def node_args node # @sg-ignore Need to add nil check here - node.children[2..-1].map do |child| + node.children[2..].map do |child| NodeChainer.chain(child, @filename, node) end end diff --git a/lib/solargraph/parser/parser_gem/node_methods.rb b/lib/solargraph/parser/parser_gem/node_methods.rb index 9b7d94827..3cbed9547 100644 --- a/lib/solargraph/parser/parser_gem/node_methods.rb +++ b/lib/solargraph/parser/parser_gem/node_methods.rb @@ -12,17 +12,17 @@ module NodeMethods # @param node [Parser::AST::Node] # @return [String] - def unpack_name(node) - pack_name(node).join("::") + def unpack_name node + pack_name(node).join('::') end # @param node [Parser::AST::Node] # @return [Array] - def pack_name(node) + def pack_name node # @type [Array] parts = [] if node.is_a?(AST::Node) - node.children.each { |n| + node.children.each do |n| if n.is_a?(AST::Node) if n.type == :cbase parts = [''] + pack_name(n) @@ -32,7 +32,7 @@ def pack_name(node) else parts.push n unless n.nil? end - } + end end parts end @@ -41,7 +41,7 @@ def pack_name(node) # @return [String, nil] def infer_literal_node_type node return nil unless node.is_a?(AST::Node) - if node.type == :str || node.type == :dstr + if %i[str dstr].include?(node.type) return '::String' elsif node.type == :array return '::Array' @@ -53,30 +53,30 @@ def infer_literal_node_type node return '::Integer' elsif node.type == :float return '::Float' - elsif node.type == :sym || node.type == :dsym + elsif %i[sym dsym].include?(node.type) return '::Symbol' elsif node.type == :regexp return '::Regexp' elsif node.type == :irange return '::Range' - elsif node.type == :true || node.type == :false + elsif %i[true false].include?(node.type) return '::Boolean' # @todo Support `nil` keyword in types - # elsif node.type == :nil - # return 'NilClass' + # elsif node.type == :nil + # return 'NilClass' end nil end # @param node [Parser::AST::Node] # @return [Position] - def get_node_start_position(node) + def get_node_start_position node Position.new(node.loc.line, node.loc.column) end # @param node [Parser::AST::Node] # @return [Position] - def get_node_end_position(node) + def get_node_end_position node Position.new(node.loc.last_line, node.loc.last_column) end @@ -86,19 +86,15 @@ def get_node_end_position(node) # @return [String] def drill_signature node, signature return signature unless node.is_a?(AST::Node) - if node.type == :const or node.type == :cbase - unless node.children[0].nil? - signature += drill_signature(node.children[0], signature) - end + if %i[const cbase].include?(node.type) + signature += drill_signature(node.children[0], signature) unless node.children[0].nil? signature += '::' unless signature.empty? signature += node.children[1].to_s - elsif node.type == :lvar or node.type == :ivar or node.type == :cvar + elsif %i[lvar ivar cvar].include?(node.type) signature += '.' unless signature.empty? signature += node.children[0].to_s elsif node.type == :send - unless node.children[0].nil? - signature += drill_signature(node.children[0], signature) - end + signature += drill_signature(node.children[0], signature) unless node.children[0].nil? signature += '.' unless signature.empty? signature += node.children[1].to_s end @@ -112,7 +108,10 @@ def convert_hash node # @sg-ignore Translate to something flow sensitive typing understands return convert_hash(node.children[0]) if node.type == :kwsplat # @sg-ignore Translate to something flow sensitive typing understands - return convert_hash(node.children[0]) if Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat + if Parser.is_ast_node?(node.children[0]) && node.children[0].type == :kwsplat + # @sg-ignore Translate to something flow sensitive typing understands + return convert_hash(node.children[0]) + end # @sg-ignore Translate to something flow sensitive typing understands return {} unless node.type == :hash result = {} @@ -151,7 +150,7 @@ def splatted_call? node end # @param nodes [Enumerable] - def any_splatted_call?(nodes) + def any_splatted_call? nodes nodes.any? { |n| splatted_call?(n) } end @@ -165,16 +164,16 @@ def call_nodes_from node result.push node if Parser.is_ast_node?(node.children[0]) && node.children[0].children.length > 2 # @sg-ignore Need to add nil check here - node.children[0].children[2..-1].each { |child| result.concat call_nodes_from(child) } + node.children[0].children[2..].each { |child| result.concat call_nodes_from(child) } end # @sg-ignore Need to add nil check here - node.children[1..-1].each { |child| result.concat call_nodes_from(child) } + node.children[1..].each { |child| result.concat call_nodes_from(child) } elsif node.type == :send result.push node result.concat call_nodes_from(node.children.first) # @sg-ignore Need to add nil check here - node.children[2..-1].each { |child| result.concat call_nodes_from(child) } - elsif [:super, :zsuper].include?(node.type) + node.children[2..].each { |child| result.concat call_nodes_from(child) } + elsif %i[super zsuper].include?(node.type) result.push node node.children.each { |child| result.concat call_nodes_from(child) } else @@ -205,44 +204,44 @@ def returns_from_method_body node # @return [Array] low-level value nodes in # value position. Does not include explicit return # statements - def value_position_nodes_only(node) + def value_position_nodes_only node DeepInference.value_position_nodes_only(node).map { |n| n || NIL_NODE } end # @param cursor [Solargraph::Source::Cursor] # @return [Parser::AST::Node, nil] def find_recipient_node cursor - return repaired_find_recipient_node(cursor) if cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '(' + if cursor.source.repaired? && cursor.source.code[cursor.offset - 1] == '(' + return repaired_find_recipient_node(cursor) + end source = cursor.source position = cursor.position offset = cursor.offset tree = if source.synchronized? - # @sg-ignore Need to add nil check here - match = source.code[0..offset-1].match(/,\s*\z/) - if match - # @sg-ignore Need to add nil check here - source.tree_at(position.line, position.column - match[0].length) - else - source.tree_at(position.line, position.column) - end - else - source.tree_at(position.line, position.column - 1) - end + # @sg-ignore Need to add nil check here + match = source.code[0..(offset - 1)].match(/,\s*\z/) + if match + # @sg-ignore Need to add nil check here + source.tree_at(position.line, position.column - match[0].length) + else + source.tree_at(position.line, position.column) + end + else + source.tree_at(position.line, position.column - 1) + end # @type [AST::Node, nil] prev = nil tree.each do |node| if node.type == :send - args = node.children[2..-1] + args = node.children[2..] # @sg-ignore Need to add nil check here if !args.empty? # @sg-ignore Need to add nil check here return node if prev && args.include?(prev) - else - if source.synchronized? - return node if source.code[0..offset-1] =~ /\(\s*\z/ && source.code[offset..-1] =~ /^\s*\)/ - else - return node if source.code[0..offset-1] =~ /\([^(]*\z/ - end + elsif source.synchronized? + return node if source.code[0..(offset - 1)] =~ /\(\s*\z/ && source.code[offset..] =~ /^\s*\)/ + elsif source.code[0..(offset - 1)] =~ /\([^(]*\z/ + return node end end prev = node @@ -255,7 +254,7 @@ def find_recipient_node cursor def repaired_find_recipient_node cursor cursor = cursor.source.cursor_at([cursor.position.line, cursor.position.column - 1]) node = cursor.source.tree_at(cursor.position.line, cursor.position.column).first - return node if node && node.type == :send + node if node && node.type == :send end # @@ -312,18 +311,15 @@ def repaired_find_recipient_node cursor # statements in value positions. module DeepInference class << self - CONDITIONAL_ALL_BUT_FIRST = [:if, :unless] - ONLY_ONE_CHILD = [:return] - FIRST_TWO_CHILDREN = [:rescue] - COMPOUND_STATEMENTS = [:begin, :kwbegin] - SKIPPABLE = [:def, :defs, :class, :sclass, :module] - FUNCTION_VALUE = [:block] - CASE_STATEMENT = [:case] + CONDITIONAL_ALL_BUT_FIRST = %i[if unless].freeze + ONLY_ONE_CHILD = [:return].freeze + FIRST_TWO_CHILDREN = [:rescue].freeze + COMPOUND_STATEMENTS = %i[begin kwbegin].freeze + SKIPPABLE = %i[def defs class sclass module].freeze + FUNCTION_VALUE = [:block].freeze + CASE_STATEMENT = [:case].freeze # @param node [AST::Node] a method body compound statement - # @param include_explicit_returns [Boolean] If true, - # include the value nodes of the parameter of the - # 'return' statements in the type returned. # @return [Array] low-level value nodes from # both nodes in value position as well as explicit # return statements in the method's closure. @@ -336,7 +332,7 @@ def from_method_body node # @return [Array] low-level value nodes in # value position. Does not include explicit return # statements - def value_position_nodes_only(node) + def value_position_nodes_only node from_value_position_statement(node, include_explicit_returns: false) end @@ -358,7 +354,7 @@ def from_value_position_statement node, include_explicit_returns: true result.concat from_value_position_compound_statement node elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) # @sg-ignore Need to add nil check here - result.concat reduce_to_value_nodes(node.children[1..-1]) + result.concat reduce_to_value_nodes(node.children[1..]) # result.push NIL_NODE unless node.children[2] elsif ONLY_ONE_CHILD.include?(node.type) result.concat reduce_to_value_nodes([node.children[0]]) @@ -370,10 +366,12 @@ def from_value_position_statement node, include_explicit_returns: true # @todo any explicit returns actually return from # scope in which the proc is run. This asssumes # that the function is executed here. - result.concat explicit_return_values_from_compound_statement(node.children[2]) if include_explicit_returns + if include_explicit_returns + result.concat explicit_return_values_from_compound_statement(node.children[2]) + end elsif CASE_STATEMENT.include?(node.type) # @sg-ignore Need to add nil check here - node.children[1..-1].each do |cc| + node.children[1..].each do |cc| if cc.nil? result.push NIL_NODE elsif cc.type == :when @@ -406,7 +404,7 @@ def from_value_position_statement node, include_explicit_returns: true # @return [Array] def from_value_position_compound_statement parent result = [] - nodes = parent.children.select{|n| n.is_a?(AST::Node)} + nodes = parent.children.select { |n| n.is_a?(AST::Node) } nodes.each_with_index do |node, idx| if node.type == :block result.concat explicit_return_values_from_compound_statement(node.children[2]) @@ -431,7 +429,10 @@ def from_value_position_compound_statement parent # value position. we already have the explicit values # from above; now we need to also gather the value # position nodes - result.concat from_value_position_statement(nodes.last, include_explicit_returns: false) if idx == nodes.length - 1 + if idx == nodes.length - 1 + result.concat from_value_position_statement(nodes.last, + include_explicit_returns: false) + end end result end @@ -447,7 +448,7 @@ def from_value_position_compound_statement parent def explicit_return_values_from_compound_statement parent return [] unless parent.is_a?(::Parser::AST::Node) result = [] - nodes = parent.children.select{|n| n.is_a?(::Parser::AST::Node)} + nodes = parent.children.select { |n| n.is_a?(::Parser::AST::Node) } nodes.each do |node| next if SKIPPABLE.include?(node.type) if node.type == :return @@ -475,7 +476,7 @@ def reduce_to_value_nodes nodes # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check elsif CONDITIONAL_ALL_BUT_FIRST.include?(node.type) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - result.concat reduce_to_value_nodes(node.children[1..-1]) + result.concat reduce_to_value_nodes(node.children[1..]) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check elsif node.type == :return # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check diff --git a/lib/solargraph/parser/parser_gem/node_processors/args_node.rb b/lib/solargraph/parser/parser_gem/node_processors/args_node.rb index ef7630921..9a22b8edd 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/args_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/args_node.rb @@ -14,17 +14,17 @@ def process node.children.each do |u| loc = get_node_location(u) locals.push Solargraph::Pin::Parameter.new( - location: loc, - closure: callable, - comments: comments_for(node), - name: u.children[0].to_s, - assignment: u.children[1], - asgn_code: u.children[1] ? region.code_for(u.children[1]) : nil, - # @sg-ignore Need to add nil check here - presence: callable.location.range, - decl: get_decl(u), - source: :parser - ) + location: loc, + closure: callable, + comments: comments_for(node), + name: u.children[0].to_s, + assignment: u.children[1], + asgn_code: u.children[1] ? region.code_for(u.children[1]) : nil, + # @sg-ignore Need to add nil check here + presence: callable.location.range, + decl: get_decl(u), + source: :parser + ) callable.parameters.push locals.last end end @@ -36,7 +36,7 @@ def process # @param callable [Pin::Callable] # @return [void] - def forward(callable) + def forward callable loc = get_node_location(node) locals.push Solargraph::Pin::Parameter.new( location: loc, diff --git a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb index b16bde064..dadc1e3ad 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/block_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/block_node.rb @@ -37,7 +37,7 @@ def other_class_eval? node.children[0].type == :send && node.children[0].children[1] == :class_eval && # @sg-ignore Need to add nil check here - [:cbase, :const].include?(node.children[0].children[0]&.type) + %i[cbase const].include?(node.children[0].children[0]&.type) end end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb index 1b9fd442d..f45f5544d 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/def_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/def_node.rb @@ -21,7 +21,7 @@ def process scope: scope, visibility: scope == :instance && name == 'initialize' ? :private : region.visibility, node: node, - source: :parser, + source: :parser ) if region.visibility == :module_function pins.push Solargraph::Pin::Method.new( @@ -34,7 +34,7 @@ def process visibility: :public, parameters: methpin.parameters, node: methpin.node, - source: :parser, + source: :parser ) pins.push Solargraph::Pin::Method.new( location: methpin.location, @@ -46,7 +46,7 @@ def process visibility: :private, parameters: methpin.parameters, node: methpin.node, - source: :parser, + source: :parser ) else pins.push methpin diff --git a/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb b/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb index 5f40457e9..09679c7f7 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/defs_node.rb @@ -11,13 +11,14 @@ def process s_visi = region.visibility s_visi = :public if s_visi == :module_function || region.scope != :class loc = get_node_location(node) - if node.children[0].is_a?(AST::Node) && node.children[0].type == :self - closure = region.closure - else - closure = Solargraph::Pin::Namespace.new( - name: unpack_name(node.children[0]) - ) - end + closure = if node.children[0].is_a?(AST::Node) && node.children[0].type == :self + region.closure + else + Solargraph::Pin::Namespace.new( + name: unpack_name(node.children[0]), + source: :parser + ) + end pins.push Solargraph::Pin::Method.new( location: loc, closure: closure, @@ -26,7 +27,7 @@ def process scope: :class, visibility: s_visi, node: node, - source: :parser, + source: :parser ) process_children region.update(closure: pins.last, scope: :class) end diff --git a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb index bf8f95635..0b9a75e77 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/if_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/if_node.rb @@ -18,7 +18,7 @@ def process location: get_node_location(condition_node), closure: region.closure, node: condition_node, - source: :parser, + source: :parser ) NodeProcessor.process(condition_node, region, pins, locals, ivars) end @@ -28,7 +28,7 @@ def process location: get_node_location(then_node), closure: region.closure, node: then_node, - source: :parser, + source: :parser ) NodeProcessor.process(then_node, region, pins, locals, ivars) end @@ -39,7 +39,7 @@ def process location: get_node_location(else_node), closure: region.closure, node: else_node, - source: :parser, + source: :parser ) NodeProcessor.process(else_node, region, pins, locals, ivars) end diff --git a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb index 0da611e22..0c4099d41 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/ivasgn_node.rb @@ -24,7 +24,8 @@ def process if named_path.is_a?(Pin::Method) ivars.push Solargraph::Pin::InstanceVariable.new( location: loc, - closure: Pin::Namespace.new(type: :module, closure: region.closure.closure, name: region.closure.name), + closure: Pin::Namespace.new(type: :module, closure: region.closure.closure, + name: region.closure.name), name: node.children[0].to_s, comments: comments_for(node), assignment: node.children[1], diff --git a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb index ab23ffa0e..aab8106aa 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/opasgn_node.rb @@ -88,7 +88,7 @@ def process_vasgn_target asgn, operator, argument argument ] send_node = node.updated(:send, send_children) - new_asgn = node.updated(asgn.type, [variable_name, send_node]) + new_asgn = node.updated(asgn.type, [variable_name, send_node]) NodeProcessor.process(new_asgn, region, pins, locals, ivars) end end diff --git a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb index d3d2cef4f..2d3d967cc 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/sclass_node.rb @@ -24,16 +24,14 @@ def process if sclass.children[0].nil? && names.last != sclass.children[1].to_s names << sclass.children[1].to_s else - names.concat [NodeMethods.unpack_name(sclass.children[0]), sclass.children[1].to_s] + names.push NodeMethods.unpack_name(sclass.children[0]), sclass.children[1].to_s end name = names.reject(&:empty?).join('::') closure = Solargraph::Pin::Namespace.new(name: name, location: region.closure.location, source: :parser) elsif sclass.is_a?(::Parser::AST::Node) && sclass.type == :const names = [region.closure.namespace, region.closure.name] also = NodeMethods.unpack_name(sclass) - if also != region.closure.name - names << also - end + names << also if also != region.closure.name name = names.reject(&:empty?).join('::') closure = Solargraph::Pin::Namespace.new(name: name, location: region.closure.location, source: :parser) else @@ -42,7 +40,7 @@ def process pins.push Solargraph::Pin::Singleton.new( location: get_node_location(node), closure: closure, - source: :parser, + source: :parser ) process_children region.update(visibility: :public, scope: :class, closure: pins.last) end diff --git a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb index 86c762c98..a9e60cb65 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/send_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/send_node.rb @@ -14,16 +14,17 @@ def process method_name = node.children[1] # :nocov: unless method_name.instance_of?(Symbol) - Solargraph.assert_or_log(:parser_method_name, "Expected method name to be a Symbol, got #{method_name.class} for node #{node.inspect}") + Solargraph.assert_or_log(:parser_method_name, + "Expected method name to be a Symbol, got #{method_name.class} for node #{node.inspect}") return process_children end # :nocov: if node.children[0].nil? - if [:private, :public, :protected].include?(method_name) + if %i[private public protected].include?(method_name) process_visibility elsif method_name == :module_function process_module_function - elsif [:attr_reader, :attr_writer, :attr_accessor].include?(method_name) + elsif %i[attr_reader attr_writer attr_accessor].include?(method_name) process_attribute elsif method_name == :include process_include @@ -44,7 +45,10 @@ def process return if process_private_class_method end elsif method_name == :require && node.children[0].to_s == '(const nil :Bundler)' - pins.push Pin::Reference::Require.new(Solargraph::Location.new(region.filename, Solargraph::Range.from_to(0, 0, 0, 0)), 'bundler/require', source: :parser) + pins.push Pin::Reference::Require.new( + Solargraph::Location.new(region.filename, + Solargraph::Range.from_to(0, 0, 0, 0)), 'bundler/require', source: :parser + ) end process_children end @@ -53,21 +57,22 @@ def process # @return [void] def process_visibility - if (node.children.length > 2) + if node.children.length > 2 # @sg-ignore Need to add nil check here - node.children[2..-1].each do |child| + node.children[2..].each do |child| # @sg-ignore Variable type could not be inferred for method_name # @type [Symbol] visibility = node.children[1] # :nocov: unless visibility.instance_of?(Symbol) - Solargraph.assert_or_log(:parser_visibility, "Expected visibility name to be a Symbol, got #{visibility.class} for node #{node.inspect}") + Solargraph.assert_or_log(:parser_visibility, + "Expected visibility name to be a Symbol, got #{visibility.class} for node #{node.inspect}") return process_children end # :nocov: - if child.is_a?(::Parser::AST::Node) && (child.type == :sym || child.type == :str) + if child.is_a?(::Parser::AST::Node) && %i[sym str].include?(child.type) name = child.children[0].to_s - matches = pins.select{ |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == region.closure.full_context.namespace && pin.context.scope == (region.scope || :instance)} + matches = pins.select { |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == region.closure.full_context.namespace && pin.context.scope == (region.scope || :instance) } matches.each do |pin| # @todo Smelly instance variable access pin.instance_variable_set(:@visibility, visibility) @@ -85,11 +90,11 @@ def process_visibility # @return [void] def process_attribute # @sg-ignore Need to add nil check here - node.children[2..-1].each do |a| + node.children[2..].each do |a| loc = get_node_location(node) clos = region.closure cmnt = comments_for(node) - if node.children[1] == :attr_reader || node.children[1] == :attr_accessor + if %i[attr_reader attr_accessor].include?(node.children[1]) pins.push Solargraph::Pin::Method.new( location: loc, closure: clos, @@ -101,63 +106,62 @@ def process_attribute source: :parser ) end - if node.children[1] == :attr_writer || node.children[1] == :attr_accessor - method_pin = Solargraph::Pin::Method.new( - location: loc, - closure: clos, - name: "#{a.children[0]}=", - comments: cmnt, - scope: region.scope || :instance, - visibility: region.visibility, - attribute: true, - source: :parser - ) - pins.push method_pin - method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last, source: :parser) - if method_pin.return_type.defined? - pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.items.map(&:rooted_tags), 'value') - end + next unless %i[attr_writer attr_accessor].include?(node.children[1]) + method_pin = Solargraph::Pin::Method.new( + location: loc, + closure: clos, + name: "#{a.children[0]}=", + comments: cmnt, + scope: region.scope || :instance, + visibility: region.visibility, + attribute: true, + source: :parser + ) + pins.push method_pin + method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last, + source: :parser) + if method_pin.return_type.defined? + pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', + pins.last.return_type.items.map(&:rooted_tags), 'value') end end end # @return [void] def process_include - if node.children[2].is_a?(AST::Node) && node.children[2].type == :const - cp = region.closure - # @sg-ignore Need to add nil check here - node.children[2..-1].each do |i| - type = region.scope == :class ? Pin::Reference::Extend : Pin::Reference::Include - pins.push type.new( - location: get_node_location(i), - closure: cp, - name: unpack_name(i), - source: :parser - ) - end + return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :const + cp = region.closure + # @sg-ignore Need to add nil check here + node.children[2..].each do |i| + type = region.scope == :class ? Pin::Reference::Extend : Pin::Reference::Include + pins.push type.new( + location: get_node_location(i), + closure: cp, + name: unpack_name(i), + source: :parser + ) end end # @return [void] def process_prepend - if node.children[2].is_a?(AST::Node) && node.children[2].type == :const - cp = region.closure - # @sg-ignore Need to add nil check here - node.children[2..-1].each do |i| - pins.push Pin::Reference::Prepend.new( - location: get_node_location(i), - closure: cp, - name: unpack_name(i), - source: :parser - ) - end + return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :const + cp = region.closure + # @sg-ignore Need to add nil check here + node.children[2..].each do |i| + pins.push Pin::Reference::Prepend.new( + location: get_node_location(i), + closure: cp, + name: unpack_name(i), + source: :parser + ) end end # @return [void] def process_extend # @sg-ignore Need to add nil check here - node.children[2..-1].each do |i| + node.children[2..].each do |i| loc = get_node_location(node) if i.type == :self pins.push Pin::Reference::Extend.new( @@ -179,18 +183,16 @@ def process_extend # @return [void] def process_require - if node.children[2].is_a?(AST::Node) && node.children[2].type == :str - path = node.children[2].children[0].to_s - pins.push Pin::Reference::Require.new(get_node_location(node), path, source: :parser) - end + return unless node.children[2].is_a?(AST::Node) && node.children[2].type == :str + path = node.children[2].children[0].to_s + pins.push Pin::Reference::Require.new(get_node_location(node), path, source: :parser) end # @return [void] def process_autoload - if node.children[3].is_a?(AST::Node) && node.children[3].type == :str - path = node.children[3].children[0].to_s - pins.push Pin::Reference::Require.new(get_node_location(node), path, source: :parser) - end + return unless node.children[3].is_a?(AST::Node) && node.children[3].type == :str + path = node.children[3].children[0].to_s + pins.push Pin::Reference::Require.new(get_node_location(node), path, source: :parser) end # @return [void] @@ -198,55 +200,55 @@ def process_module_function if node.children[2].nil? # @todo Smelly instance variable access region.instance_variable_set(:@visibility, :module_function) - elsif node.children[2].type == :sym || node.children[2].type == :str + elsif %i[sym str].include?(node.children[2].type) # @sg-ignore Need to add nil check here - node.children[2..-1].each do |x| + node.children[2..].each do |x| cn = x.children[0].to_s # @type [Pin::Method, nil] ref = pins.find { |p| p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == cn } - unless ref.nil? - pins.delete ref - mm = Solargraph::Pin::Method.new( - location: ref.location, - closure: ref.closure, - name: ref.name, - parameters: ref.parameters, - comments: ref.comments, - scope: :class, - visibility: :public, - node: ref.node, + next if ref.nil? + pins.delete ref + mm = Solargraph::Pin::Method.new( + location: ref.location, + closure: ref.closure, + name: ref.name, + parameters: ref.parameters, + comments: ref.comments, + scope: :class, + visibility: :public, + node: ref.node, + source: :parser + ) + cm = Solargraph::Pin::Method.new( + location: ref.location, + closure: ref.closure, + name: ref.name, + parameters: ref.parameters, + comments: ref.comments, + scope: :instance, + visibility: :private, + node: ref.node, + source: :parser + ) + pins.push mm, cm + ivars.select { |pin| pin.is_a?(Pin::InstanceVariable) && pin.closure.path == ref.path }.each do |ivar| + ivars.delete ivar + ivars.push Solargraph::Pin::InstanceVariable.new( + location: ivar.location, + closure: cm, + name: ivar.name, + comments: ivar.comments, + assignment: ivar.assignment, + source: :parser + ) + ivars.push Solargraph::Pin::InstanceVariable.new( + location: ivar.location, + closure: mm, + name: ivar.name, + comments: ivar.comments, + assignment: ivar.assignment, source: :parser ) - cm = Solargraph::Pin::Method.new( - location: ref.location, - closure: ref.closure, - name: ref.name, - parameters: ref.parameters, - comments: ref.comments, - scope: :instance, - visibility: :private, - node: ref.node, - source: :parser) - pins.push mm, cm - ivars.select{|pin| pin.is_a?(Pin::InstanceVariable) && pin.closure.path == ref.path}.each do |ivar| - ivars.delete ivar - ivars.push Solargraph::Pin::InstanceVariable.new( - location: ivar.location, - closure: cm, - name: ivar.name, - comments: ivar.comments, - assignment: ivar.assignment, - source: :parser - ) - ivars.push Solargraph::Pin::InstanceVariable.new( - location: ivar.location, - closure: mm, - name: ivar.name, - comments: ivar.comments, - assignment: ivar.assignment, - source: :parser - ) - end end end elsif node.children[2].type == :def @@ -256,17 +258,19 @@ def process_module_function # @return [void] def process_private_constant - if node.children[2] && (node.children[2].type == :sym || node.children[2].type == :str) - cn = node.children[2].children[0].to_s - ref = pins.select{|p| [Solargraph::Pin::Namespace, Solargraph::Pin::Constant].include?(p.class) && p.namespace == region.closure.full_context.namespace && p.name == cn}.first - # HACK: Smelly instance variable access - ref.instance_variable_set(:@visibility, :private) unless ref.nil? - end + return unless node.children[2] && %i[sym str].include?(node.children[2].type) + cn = node.children[2].children[0].to_s + ref = pins.select do |p| + [Solargraph::Pin::Namespace, + Solargraph::Pin::Constant].include?(p.class) && p.namespace == region.closure.full_context.namespace && p.name == cn + end.first + # HACK: Smelly instance variable access + ref&.instance_variable_set(:@visibility, :private) end # @return [void] def process_alias_method - loc = get_node_location(node) + get_node_location(node) pins.push Solargraph::Pin::MethodAlias.new( location: get_node_location(node), closure: region.closure, @@ -279,10 +283,12 @@ def process_alias_method # @return [Boolean] def process_private_class_method - if node.children[2].type == :sym || node.children[2].type == :str - ref = pins.select { |p| p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == node.children[2].children[0].to_s }.first + if %i[sym str].include?(node.children[2].type) + ref = pins.select do |p| + p.is_a?(Pin::Method) && p.namespace == region.closure.full_context.namespace && p.name == node.children[2].children[0].to_s + end.first # HACK: Smelly instance variable access - ref.instance_variable_set(:@visibility, :private) unless ref.nil? + ref&.instance_variable_set(:@visibility, :private) false else process_children region.update(scope: :class, visibility: :private) diff --git a/lib/solargraph/parser/parser_gem/node_processors/until_node.rb b/lib/solargraph/parser/parser_gem/node_processors/until_node.rb index bcfae287d..2e091f41d 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/until_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/until_node.rb @@ -18,7 +18,7 @@ def process closure: region.closure, node: node, comments: comments_for(node), - source: :parser, + source: :parser ) process_children region end diff --git a/lib/solargraph/parser/parser_gem/node_processors/when_node.rb b/lib/solargraph/parser/parser_gem/node_processors/when_node.rb index b2b11dec1..915eb57e6 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/when_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/when_node.rb @@ -12,7 +12,7 @@ def process location: get_node_location(node), closure: region.closure, node: node, - source: :parser, + source: :parser ) process_children end diff --git a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb index 38ccc53b5..6c4fe33d8 100644 --- a/lib/solargraph/parser/parser_gem/node_processors/while_node.rb +++ b/lib/solargraph/parser/parser_gem/node_processors/while_node.rb @@ -22,7 +22,7 @@ def process closure: region.closure, node: node, comments: comments_for(node), - source: :parser, + source: :parser ) process_children region end diff --git a/lib/solargraph/parser/snippet.rb b/lib/solargraph/parser/snippet.rb index 1ea6bd6d9..7282a44d6 100644 --- a/lib/solargraph/parser/snippet.rb +++ b/lib/solargraph/parser/snippet.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Parser class Snippet diff --git a/lib/solargraph/pin/base.rb b/lib/solargraph/pin/base.rb index d0e53d741..240d46b44 100644 --- a/lib/solargraph/pin/base.rb +++ b/lib/solargraph/pin/base.rb @@ -46,7 +46,8 @@ def presence_certain? # @param docstring [YARD::Docstring, nil] # @param directives [::Array, nil] # @param combine_priority [::Numeric, nil] See attr_reader for combine_priority - def initialize location: nil, type_location: nil, closure: nil, source: nil, name: '', comments: '', docstring: nil, directives: nil, combine_priority: nil + def initialize location: nil, type_location: nil, closure: nil, source: nil, name: '', comments: '', + docstring: nil, directives: nil, combine_priority: nil @location = location @type_location = type_location @closure = closure @@ -60,7 +61,6 @@ def initialize location: nil, type_location: nil, closure: nil, source: nil, nam # @type [ComplexType, ComplexType::UniqueType, nil] @binder = nil - assert_source_provided assert_location_provided end @@ -69,12 +69,16 @@ def initialize location: nil, type_location: nil, closure: nil, source: nil, nam def assert_location_provided return unless best_location.nil? && %i[yardoc source rbs].include?(source) - Solargraph.assert_or_log(:best_location, "Neither location nor type_location provided - #{path} #{source} #{self.class}") + Solargraph.assert_or_log(:best_location, + "Neither location nor type_location provided - #{path} #{source} #{self.class}") end # @return [Pin::Closure, nil] def closure - Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure + unless @closure + Solargraph.assert_or_log(:closure, + "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") + end @closure end @@ -82,7 +86,7 @@ def closure # @param attrs [Hash{::Symbol => Object}] # # @return [self] - def combine_with(other, attrs={}) + def combine_with other, attrs = {} priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? @@ -101,7 +105,9 @@ def combine_with(other, attrs={}) combine_priority: combine_priority }.merge(attrs) assert_same_macros(other) - logger.debug { "Base#combine_with(path=#{path}) - other.comments=#{other.comments.inspect}, self.comments = #{self.comments}" } + logger.debug do + "Base#combine_with(path=#{path}) - other.comments=#{other.comments.inspect}, self.comments = #{comments}" + end out = self.class.new(**new_attrs) out.reset_generated! out @@ -110,7 +116,7 @@ def combine_with(other, attrs={}) # @param other [self] # @return [self, nil] Returns either the pin chosen based on priority or nil # A nil return means that the combination process must proceed - def choose_priority(other) + def choose_priority other if combine_priority.nil? && !other.combine_priority.nil? return other elsif other.combine_priority.nil? && !combine_priority.nil? @@ -130,7 +136,7 @@ def choose_priority(other) # @param attr [::Symbol] # @sg-ignore # @return [undefined] - def choose_longer(other, attr) + def choose_longer other, attr # @type [undefined] val1 = send(attr) # @type [undefined] @@ -143,22 +149,22 @@ def choose_longer(other, attr) # @param other [self] # # @return [::Array, nil] - def combine_directives(other) - return self.directives if other.directives.empty? + def combine_directives other + return directives if other.directives.empty? return other.directives if directives.empty? (directives + other.directives).uniq end # @param other [self] # @return [Pin::Closure, nil] - def combine_closure(other) + def combine_closure other choose_pin_attr_with_same_name(other, :closure) end # @param other [self] # @sg-ignore @type should override probed type # @return [String] - def combine_name(other) + def combine_name other if needs_consistent_name? || other.needs_consistent_name? assert_same(other, :name) else @@ -189,14 +195,9 @@ def needs_consistent_name? true end - # @sg-ignore def should infer as symbol - "Not enough arguments to Module#protected" - protected def equality_fields - [name, location, type_location, closure, source] - end - # @param other [self] # @return [ComplexType] - def combine_return_type(other) + def combine_return_type other if return_type.undefined? other.return_type elsif other.return_type.undefined? @@ -211,7 +212,9 @@ def combine_return_type(other) return_type else all_items = return_type.items + other.return_type.items - if all_items.any? { |item| item.selfy? } && all_items.any? { |item| item.rooted_tag == context.reduce_class_type.rooted_tag } + if all_items.any?(&:selfy?) && all_items.any? do |item| + item.rooted_tag == context.reduce_class_type.rooted_tag + end # assume this was a declaration that should have said 'self' all_items.delete_if { |item| item.rooted_tag == context.reduce_class_type.rooted_tag } end @@ -234,14 +237,17 @@ def dodgy_return_type_source? # # @sg-ignore # @return [undefined, nil] - def choose(other, attr) + def choose other, attr results = [self, other].map(&attr).compact # true and false are different classes and can't be sorted - return true if results.any? { |r| r == true || r == false } + + # @sg-ignore Wrong argument type for Array#include?: object + # expected Boolean, received Proc + return true if results.any? { |r| [true, false].include?(r) } return results.first if results.any? { |r| r.is_a? AST::Node } results.min - rescue - STDERR.puts("Problem handling #{attr} for \n#{self.inspect}\n and \n#{other.inspect}\n\n#{self.send(attr).inspect} vs #{other.send(attr).inspect}") + rescue StandardError + warn("Problem handling #{attr} for \n#{inspect}\n and \n#{other.inspect}\n\n#{send(attr).inspect} vs #{other.send(attr).inspect}") raise end @@ -249,7 +255,7 @@ def choose(other, attr) # @param attr [::Symbol] # @sg-ignore # @return [undefined] - def choose_node(other, attr) + def choose_node other, attr if other.object_id < attr.object_id other.send(attr) else @@ -261,9 +267,9 @@ def choose_node(other, attr) # @param attr [::Symbol] # @sg-ignore # @return [undefined] - def prefer_rbs_location(other, attr) + def prefer_rbs_location other, attr if rbs_location? && !other.rbs_location? - self.send(attr) + send(attr) elsif !rbs_location? && other.rbs_location? other.send(attr) else @@ -278,8 +284,8 @@ def rbs_location? # @param other [self] # @return [void] - def assert_same_macros(other) - return unless self.source == :yardoc && other.source == :yardoc + def assert_same_macros other + return unless source == :yardoc && other.source == :yardoc assert_same_count(other, :macros) # @param [YARD::Tags::MacroDirective] assert_same_array_content(other, :macros) { |macro| macro.tag.name } @@ -289,7 +295,7 @@ def assert_same_macros(other) # @param attr [::Symbol] # @return [void] # @todo strong typechecking should complain when there are no block-related tags - def assert_same_array_content(other, attr, &block) + def assert_same_array_content other, attr, &block arr1 = send(attr) raise "Expected #{attr} on #{self} to be an Enumerable, got #{arr1.class}" unless arr1.is_a?(::Enumerable) # @type arr1 [::Enumerable] @@ -303,7 +309,7 @@ def assert_same_array_content(other, attr, &block) values2 = arr2.map(&block) # @sg-ignore return arr1 if values1 == values2 - Solargraph.assert_or_log("combine_with_#{attr}".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}", "Inconsistent #{attr.inspect} values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self values = #{values1}\nother values =#{attr} = #{values2}") arr1 end @@ -312,15 +318,15 @@ def assert_same_array_content(other, attr, &block) # @param attr [::Symbol] # # @return [::Enumerable] - def assert_same_count(other, attr) + def assert_same_count other, attr # @type [::Enumerable] - arr1 = self.send(attr) + arr1 = send(attr) raise "Expected #{attr} on #{self} to be an Enumerable, got #{arr1.class}" unless arr1.is_a?(::Enumerable) # @type [::Enumerable] arr2 = other.send(attr) raise "Expected #{attr} on #{other} to be an Enumerable, got #{arr2.class}" unless arr2.is_a?(::Enumerable) return arr1 if arr1.count == arr2.count - Solargraph.assert_or_log("combine_with_#{attr}".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}", "Inconsistent #{attr.inspect} count value between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{arr1.inspect}\nother.#{attr} = #{arr2.inspect}") arr1 end @@ -330,16 +336,16 @@ def assert_same_count(other, attr) # # @sg-ignore # @return [undefined] - def assert_same(other, attr) + def assert_same other, attr if other.nil? - Solargraph.assert_or_log("combine_with_#{attr}_nil".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}_nil", "Other was passed in nil in assert_same on #{self}") return send(attr) end val1 = send(attr) val2 = other.send(attr) return val1 if val1 == val2 - Solargraph.assert_or_log("combine_with_#{attr}".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}", "Inconsistent #{attr.inspect} values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}") val1 end @@ -348,15 +354,17 @@ def assert_same(other, attr) # @param attr [::Symbol] # @sg-ignore # @return [undefined] - def choose_pin_attr_with_same_name(other, attr) + def choose_pin_attr_with_same_name other, attr # @type [Pin::Base, nil] val1 = send(attr) # @type [Pin::Base, nil] val2 = other.send(attr) - raise "Expected pin for #{attr} on\n#{self.inspect},\ngot #{val1.inspect}" unless val1.nil? || val1.is_a?(Pin::Base) - raise "Expected pin for #{attr} on\n#{other.inspect},\ngot #{val2.inspect}" unless val2.nil? || val2.is_a?(Pin::Base) + raise "Expected pin for #{attr} on\n#{inspect},\ngot #{val1.inspect}" unless val1.nil? || val1.is_a?(Pin::Base) + unless val2.nil? || val2.is_a?(Pin::Base) + raise "Expected pin for #{attr} on\n#{other.inspect},\ngot #{val2.inspect}" + end if val1&.name != val2&.name - Solargraph.assert_or_log("combine_with_#{attr}_name".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}_name", "Inconsistent #{attr.inspect} name values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}") end choose_pin_attr(other, attr) @@ -367,14 +375,14 @@ def choose_pin_attr_with_same_name(other, attr) # # @sg-ignore Missing @return tag for Solargraph::Pin::Base#choose_pin_attr # @return [undefined] - def choose_pin_attr(other, attr) + def choose_pin_attr other, attr # @type [Pin::Base, nil] val1 = send(attr) # @type [Pin::Base, nil] val2 = other.send(attr) if val1.class != val2.class # :nocov: - Solargraph.assert_or_log("combine_with_#{attr}_class".to_sym, + Solargraph.assert_or_log(:"combine_with_#{attr}_class", "Inconsistent #{attr.inspect} class values between \nself =#{inspect} and \nother=#{other.inspect}:\n\n self.#{attr} = #{val1.inspect}\nother.#{attr} = #{val2.inspect}") return val1 # :nocov: @@ -406,10 +414,9 @@ def comments # @param generics_to_resolve [Enumerable] # @param return_type_context [ComplexType, ComplexType::UniqueType, nil] - # @param context [ComplexType] # @param resolved_generic_values [Hash{String => ComplexType}] # @return [self] - def resolve_generics_from_context(generics_to_resolve, return_type_context = nil, resolved_generic_values: {}) + def resolve_generics_from_context generics_to_resolve, return_type_context = nil, resolved_generic_values: {} proxy return_type.resolve_generics_from_context(generics_to_resolve, return_type_context, resolved_generic_values: resolved_generic_values) @@ -418,7 +425,7 @@ def resolve_generics_from_context(generics_to_resolve, return_type_context = nil # @yieldparam [ComplexType] # @yieldreturn [ComplexType] # @return [self] - def transform_types(&transform) + def transform_types &transform proxy return_type.transform(&transform) end @@ -430,7 +437,7 @@ def transform_types(&transform) # @param context_type [ComplexType] The receiver type # @return [self] def resolve_generics definitions, context_type - transform_types { |t| t.resolve_generics(definitions, context_type) if t } + transform_types { |t| t&.resolve_generics(definitions, context_type) } end def all_rooted? @@ -439,7 +446,7 @@ def all_rooted? # @param generics_to_erase [::Array] # @return [self] - def erase_generics(generics_to_erase) + def erase_generics generics_to_erase return self if generics_to_erase.empty? transform_types { |t| t.erase_generics(generics_to_erase) } end @@ -482,15 +489,18 @@ def best_location # @param other [Solargraph::Pin::Base, Object] # @return [Boolean] def nearly? other - self.class == other.class && + instance_of?(other.class) && # @sg-ignore Translate to something flow sensitive typing understands name == other.name && # @sg-ignore flow sensitive typing needs to handle attrs (closure == other.closure || (closure && closure.nearly?(other.closure))) && # @sg-ignore Translate to something flow sensitive typing understands (comments == other.comments || - # @sg-ignore Translate to something flow sensitive typing understands - (((maybe_directives? == false && other.maybe_directives? == false) || compare_directives(directives, other.directives)) && + # @sg-ignore Translate to something flow sensitive typing understands + (((maybe_directives? == false && other.maybe_directives? == false) || + compare_directives(directives, + # @sg-ignore Translate to something flow sensitive typing understands + other.directives)) && # @sg-ignore Translate to something flow sensitive typing understands compare_docstring_tags(docstring, other.docstring)) ) @@ -573,7 +583,8 @@ def probe api_map # @param api_map [ApiMap] # @return [ComplexType, ComplexType::UniqueType] def infer api_map - Solargraph.assert_or_log(:pin_infer, 'WARNING: Pin #infer methods are deprecated. Use #typify or #probe instead.') + Solargraph.assert_or_log(:pin_infer, + 'WARNING: Pin #infer methods are deprecated. Use #typify or #probe instead.') type = typify(api_map) return type unless type.undefined? probe api_map @@ -643,7 +654,7 @@ def type_desc rbs = return_type.rooted_tags if return_type.name == 'Class' if path if rbs - path + ' ' + rbs + "#{path} #{rbs}" else path end @@ -666,7 +677,7 @@ def desc # @return [String] def inspect - "#<#{self.class} `#{self.inner_desc}`#{all_location_text} via #{source.inspect}>" + "#<#{self.class} `#{inner_desc}`#{all_location_text} via #{source.inspect}>" end # @return [String] @@ -684,6 +695,11 @@ def all_location_text protected + # @sg-ignore def should infer as symbol - "Not enough arguments to Module#protected" + def equality_fields + [name, location, type_location, closure, source] + end + # @return [Boolean] attr_writer :probed @@ -693,9 +709,7 @@ def all_location_text # @return [ComplexType, ComplexType::UniqueType, nil] attr_writer :return_type - attr_writer :docstring - - attr_writer :directives + attr_writer :docstring, :directives private @@ -717,13 +731,13 @@ def parse_comments # True if two docstrings have the same tags, regardless of any other # differences. # - # @param d1 [YARD::Docstring] - # @param d2 [YARD::Docstring] + # @param docstring1 [YARD::Docstring] + # @param docstring2 [YARD::Docstring] # @return [Boolean] - def compare_docstring_tags d1, d2 - return false if d1.tags.length != d2.tags.length - d1.tags.each_index do |i| - return false unless compare_tags(d1.tags[i], d2.tags[i]) + def compare_docstring_tags docstring1, docstring2 + return false if docstring1.tags.length != docstring2.tags.length + docstring1.tags.each_index do |i| + return false unless compare_tags(docstring1.tags[i], docstring2.tags[i]) end true end @@ -743,7 +757,7 @@ def compare_directives dir1, dir2 # @param tag2 [YARD::Tags::Tag] # @return [Boolean] def compare_tags tag1, tag2 - tag1.class == tag2.class && + tag1.instance_of?(tag2.class) && tag1.tag_name == tag2.tag_name && tag1.text == tag2.text && tag1.name == tag2.name && @@ -754,7 +768,7 @@ def compare_tags tag1, tag2 def collect_macros return [] unless maybe_directives? parse = Solargraph::Source.parse_docstring(comments) - parse.directives.select{ |d| d.tag.tag_name == 'macro' } + parse.directives.select { |d| d.tag.tag_name == 'macro' } end end end diff --git a/lib/solargraph/pin/base_variable.rb b/lib/solargraph/pin/base_variable.rb index 1e021f86b..c7945e599 100644 --- a/lib/solargraph/pin/base_variable.rb +++ b/lib/solargraph/pin/base_variable.rb @@ -45,9 +45,9 @@ class BaseVariable < Base # @see https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#union-types # @see https://en.wikipedia.org/wiki/Intersection_type#TypeScript_example # @param presence [Range, nil] - # @param presence_certain [Boolean] + # @param [Hash{Symbol => Object}] splat def initialize assignment: nil, assignments: [], mass_assignment: nil, - presence: nil, presence_certain: false, return_type: nil, + presence: nil, return_type: nil, intersection_return_type: nil, exclude_return_type: nil, **splat super(**splat) @@ -58,7 +58,6 @@ def initialize assignment: nil, assignments: [], mass_assignment: nil, @intersection_return_type = intersection_return_type @exclude_return_type = exclude_return_type @presence = presence - @presence_certain = presence_certain end # @param presence [Range] @@ -83,44 +82,33 @@ def reset_generated! super end - def combine_with(other, attrs={}) + def combine_with other, attrs = {} new_assignments = combine_assignments(other) new_attrs = attrs.merge({ - # default values don't exist in RBS parameters; it just - # tells you if the arg is optional or not. Prefer a - # provided value if we have one here since we can't rely on - # it from RBS so we can infer from it and typecheck on it. - assignment: choose(other, :assignment), - assignments: new_assignments, - mass_assignment: combine_mass_assignment(other), - return_type: combine_return_type(other), - intersection_return_type: combine_types(other, :intersection_return_type), - exclude_return_type: combine_types(other, :exclude_return_type), - presence: combine_presence(other), - presence_certain: combine_presence_certain(other) - }) + # default values don't exist in RBS parameters; it just + # tells you if the arg is optional or not. Prefer a + # provided value if we have one here since we can't rely on + # it from RBS so we can infer from it and typecheck on it. + assignment: choose(other, :assignment), + assignments: new_assignments, + mass_assignment: combine_mass_assignment(other), + return_type: combine_return_type(other), + intersection_return_type: combine_types(other, :intersection_return_type), + exclude_return_type: combine_types(other, :exclude_return_type), + presence: combine_presence(other) + }) super(other, new_attrs) end # @param other [self] # # @return [Array(AST::Node, Integer), nil] - def combine_mass_assignment(other) + def combine_mass_assignment other # @todo pick first non-nil arbitrarily - we don't yet support # mass assignment merging mass_assignment || other.mass_assignment end - # If a certain pin is being combined with an uncertain pin, we - # end up with a certain result - # - # @param other [self] - # - # @return [Boolean] - def combine_presence_certain(other) - presence_certain? || other.presence_certain? - end - # @return [Parser::AST::Node, nil] def assignment @assignment ||= assignments.last @@ -129,7 +117,7 @@ def assignment # @param other [self] # # @return [::Array] - def combine_assignments(other) + def combine_assignments other (other.assignments + assignments).uniq end @@ -161,7 +149,7 @@ def variable? # @param parent_node [Parser::AST::Node] # @param api_map [ApiMap] # @return [::Array] - def return_types_from_node(parent_node, api_map) + def return_types_from_node parent_node, api_map types = [] value_position_nodes_only(parent_node).each do |node| # Nil nodes may not have a location @@ -242,7 +230,7 @@ def presence_certain? # @param other_loc [Location] # @sg-ignore flow sensitive typing needs to handle attrs - def starts_at?(other_loc) + def starts_at? other_loc location&.filename == other_loc.filename && presence && # @sg-ignore flow sensitive typing needs to handle attrs @@ -254,7 +242,7 @@ def starts_at?(other_loc) # @param other [self] # # @return [Range, nil] - def combine_presence(other) + def combine_presence other return presence || other.presence if presence.nil? || other.presence.nil? # @sg-ignore flow sensitive typing needs to handle attrs @@ -263,14 +251,14 @@ def combine_presence(other) # @param other [self] # @return [Pin::Closure, nil] - def combine_closure(other) - return closure if self.closure == other.closure + def combine_closure other + return closure if closure == other.closure # choose first defined, as that establishes the scope of the variable if closure.nil? || other.closure.nil? Solargraph.assert_or_log(:varible_closure_missing) do - "One of the local variables being combined is missing a closure: " \ - "#{self.inspect} vs #{other.inspect}" + 'One of the local variables being combined is missing a closure: ' \ + "#{inspect} vs #{other.inspect}" end return closure || other.closure end @@ -290,7 +278,7 @@ def combine_closure(other) # @param other_closure [Pin::Closure] # @param other_loc [Location] - def visible_at?(other_closure, other_loc) + def visible_at? other_closure, other_loc # @sg-ignore flow sensitive typing needs to handle attrs location.filename == other_loc.filename && # @sg-ignore flow sensitive typing needs to handle attrs @@ -298,10 +286,6 @@ def visible_at?(other_closure, other_loc) visible_in_closure?(other_closure) end - def presence_certain? - @presence_certain - end - protected attr_accessor :exclude_return_type, :intersection_return_type @@ -315,40 +299,13 @@ def presence_certain? # @param raw_return_type [ComplexType, ComplexType::UniqueType] # # @return [ComplexType, ComplexType::UniqueType] - def adjust_type(api_map, raw_return_type) + def adjust_type api_map, raw_return_type qualified_exclude = exclude_return_type&.qualify(api_map, *(closure&.gates || [''])) minus_exclusions = raw_return_type.exclude qualified_exclude, api_map qualified_intersection = intersection_return_type&.qualify(api_map, *(closure&.gates || [''])) minus_exclusions.intersect_with qualified_intersection, api_map end - # @param other [self] - # @return [Pin::Closure, nil] - def combine_closure(other) - return closure if self.closure == other.closure - - # choose first defined, as that establishes the scope of the variable - if closure.nil? || other.closure.nil? - Solargraph.assert_or_log(:varible_closure_missing) do - "One of the local variables being combined is missing a closure: " \ - "#{self.inspect} vs #{other.inspect}" - end - return closure || other.closure - end - - # @sg-ignore Need to add nil check here - if closure.location.nil? || other.closure.location.nil? - # @sg-ignore Need to add nil check here - return closure.location.nil? ? other.closure : closure - end - - # if filenames are different, this will just pick one - # @sg-ignore flow sensitive typing needs to handle attrs - return closure if closure.location <= other.closure.location - - other.closure - end - # See if this variable is visible within 'viewing_closure' # # @param viewing_closure [Pin::Closure] @@ -381,7 +338,7 @@ def visible_in_closure? viewing_closure # @param other [self] # @return [ComplexType, nil] - def combine_return_type(other) + def combine_return_type other combine_types(other, :return_type) end @@ -389,7 +346,7 @@ def combine_return_type(other) # @param attr [::Symbol] # # @return [ComplexType, nil] - def combine_types(other, attr) + def combine_types other, attr # @type [ComplexType, nil] type1 = send(attr) # @type [ComplexType, nil] diff --git a/lib/solargraph/pin/block.rb b/lib/solargraph/pin/block.rb index 39f0d6495..1ad503317 100644 --- a/lib/solargraph/pin/block.rb +++ b/lib/solargraph/pin/block.rb @@ -15,6 +15,7 @@ class Block < Callable # @param node [Parser::AST::Node, nil] # @param context [ComplexType, nil] # @param args [::Array] + # @param [Hash{Symbol => Object}] splat def initialize receiver: nil, args: [], context: nil, node: nil, **splat super(**splat, parameters: args) @receiver = receiver @@ -44,7 +45,7 @@ def context # @param parameters [::Array] # # @return [::Array] - def destructure_yield_types(yield_types, parameters) + def destructure_yield_types yield_types, parameters # yielding a tuple into a block will destructure the tuple if yield_types.length == 1 yield_type = yield_types.first @@ -55,7 +56,7 @@ def destructure_yield_types(yield_types, parameters) # @param api_map [ApiMap] # @return [::Array] - def typify_parameters(api_map) + def typify_parameters api_map chain = Parser.chain(receiver, filename, node) # @sg-ignore Need to add nil check here clip = api_map.clip_at(location.filename, location.range.start) diff --git a/lib/solargraph/pin/breakable.rb b/lib/solargraph/pin/breakable.rb index 7cf6df9ab..f5e4bfd4a 100644 --- a/lib/solargraph/pin/breakable.rb +++ b/lib/solargraph/pin/breakable.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Pin # Mix-in for pins which enclose code which the 'break' statement diff --git a/lib/solargraph/pin/callable.rb b/lib/solargraph/pin/callable.rb index 034cef03f..2ad4bbf62 100644 --- a/lib/solargraph/pin/callable.rb +++ b/lib/solargraph/pin/callable.rb @@ -14,6 +14,7 @@ class Callable < Closure # @param block [Signature, nil] # @param return_type [ComplexType, nil] # @param parameters [::Array] + # @param [Hash{Symbol => Object}] splat def initialize block: nil, return_type: nil, parameters: [], **splat super(**splat) @block = block @@ -36,7 +37,7 @@ def method_namespace # @param other [self] # # @return [Pin::Signature, nil] - def combine_blocks(other) + def combine_blocks other if block.nil? other.block elsif other.block.nil? @@ -51,10 +52,10 @@ def combine_blocks(other) # @param attrs [Hash{Symbol => Object}] # # @return [self] - def combine_with(other, attrs={}) + def combine_with other, attrs = {} new_attrs = { block: combine_blocks(other), - return_type: combine_return_type(other), + return_type: combine_return_type(other) }.merge(attrs) new_attrs[:parameters] = choose_parameters(other).clone.freeze unless new_attrs.key?(:parameters) super(other, new_attrs) @@ -72,8 +73,10 @@ def generics # @param other [self] # # @return [Array] - def choose_parameters(other) - raise "Trying to combine two pins with different arities - \nself =#{inspect}, \nother=#{other.inspect}, \n\n self.arity=#{self.arity}, \nother.arity=#{other.arity}" if other.arity != arity + def choose_parameters other + if other.arity != arity + raise "Trying to combine two pins with different arities - \nself =#{inspect}, \nother=#{other.inspect}, \n\n self.arity=#{arity}, \nother.arity=#{other.arity}" + end # @param param [Pin::Parameter] # @param other_param [Pin::Parameter] parameters.zip(other.parameters).map do |param, other_param| @@ -129,16 +132,15 @@ def full_type_arity # @param return_type_context [ComplexType, nil] # @param yield_arg_types [Array, nil] # @param yield_return_type_context [ComplexType, nil] - # @param context [ComplexType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] # # @return [self] - def resolve_generics_from_context(generics_to_resolve, + def resolve_generics_from_context generics_to_resolve, arg_types = nil, return_type_context = nil, yield_arg_types = nil, yield_return_type_context = nil, - resolved_generic_values: {}) + resolved_generic_values: {} callable = super(generics_to_resolve, return_type_context, resolved_generic_values: resolved_generic_values) callable.parameters = callable.parameters.each_with_index.map do |param, i| if arg_types.nil? @@ -149,10 +151,12 @@ def resolve_generics_from_context(generics_to_resolve, resolved_generic_values: resolved_generic_values) end end - callable.block = block.resolve_generics_from_context(generics_to_resolve, - yield_arg_types, - yield_return_type_context, - resolved_generic_values: resolved_generic_values) if callable.block? + if callable.block? + callable.block = block.resolve_generics_from_context(generics_to_resolve, + yield_arg_types, + yield_return_type_context, + resolved_generic_values: resolved_generic_values) + end callable end @@ -171,7 +175,7 @@ def typify api_map # @sg-ignore Need to add nil check here # @return [String] def method_name - raise "closure was nil in #{self.inspect}" if closure.nil? + raise "closure was nil in #{inspect}" if closure.nil? # @sg-ignore Need to add nil check here @method_name ||= closure.name end @@ -181,16 +185,15 @@ def method_name # @param return_type_context [ComplexType, nil] # @param yield_arg_types [Array, nil] # @param yield_return_type_context [ComplexType, nil] - # @param context [ComplexType, nil] # @param resolved_generic_values [Hash{String => ComplexType}] # # @return [self] - def resolve_generics_from_context_until_complete(generics_to_resolve, + def resolve_generics_from_context_until_complete generics_to_resolve, arg_types = nil, return_type_context = nil, yield_arg_types = nil, yield_return_type_context = nil, - resolved_generic_values: {}) + resolved_generic_values: {} # See # https://github.com/soutaro/steep/tree/master/lib/steep/type_inference # and @@ -208,7 +211,7 @@ def resolve_generics_from_context_until_complete(generics_to_resolve, resolved_generic_values: resolved_generic_values) if last_resolved_generic_values == resolved_generic_values # erase anything unresolved - return new_pin.erase_generics(self.generics) + return new_pin.erase_generics(generics) end new_pin.resolve_generics_from_context_until_complete(generics_to_resolve, arg_types, @@ -221,7 +224,7 @@ def resolve_generics_from_context_until_complete(generics_to_resolve, # @yieldparam [ComplexType] # @yieldreturn [ComplexType] # @return [self] - def transform_types(&transform) + def transform_types &transform # @todo 'super' alone should work here I think, but doesn't typecheck at level typed callable = super(&transform) callable.block = block.transform_types(&transform) if block? @@ -246,11 +249,6 @@ def arity_matches? arguments, with_block true end - def reset_generated! - super - @parameters.each(&:reset_generated!) - end - # @return [Integer] def mandatory_positional_param_count parameters.count(&:arg?) @@ -258,12 +256,11 @@ def mandatory_positional_param_count # @return [String] def parameters_to_rbs - # @sg-ignore Need to add nil check here - rbs_generics + '(' + parameters.map { |param| param.to_rbs }.join(', ') + ') ' + (block.nil? ? '' : '{ ' + block.to_rbs + ' } ') + "#{rbs_generics}(#{parameters.map(&:to_rbs).join(', ')}) #{"{ #{block.to_rbs} } " unless block.nil?}" end def to_rbs - parameters_to_rbs + '-> ' + (return_type&.to_rbs || 'untyped') + "#{parameters_to_rbs}-> #{return_type&.to_rbs || 'untyped'}" end def block? diff --git a/lib/solargraph/pin/closure.rb b/lib/solargraph/pin/closure.rb index 44265dcab..4b5738cfc 100644 --- a/lib/solargraph/pin/closure.rb +++ b/lib/solargraph/pin/closure.rb @@ -9,7 +9,8 @@ class Closure < CompoundStatement # @param scope [::Symbol] :class or :instance # @param generics [::Array, nil] # @param generic_defaults [Hash{String => ComplexType}] - def initialize scope: :class, generics: nil, generic_defaults: {}, **splat + # @param [Hash{Symbol => Object}] splat + def initialize scope: :class, generics: nil, generic_defaults: {}, **splat super(**splat) @scope = scope @generics = generics @@ -25,10 +26,10 @@ def generic_defaults # @param attrs [Hash{Symbol => Object}] # # @return [self] - def combine_with(other, attrs={}) + def combine_with other, attrs = {} new_attrs = { scope: assert_same(other, :scope), - generics: generics.empty? ? other.generics : generics, + generics: generics.empty? ? other.generics : generics }.merge(attrs) super(other, new_attrs) end @@ -61,7 +62,7 @@ def to_rbs def rbs_generics return '' if generics.empty? - '[' + generics.map { |gen| gen.to_s }.join(', ') + '] ' + "[#{generics.map(&:to_s).join(', ')}] " end end end diff --git a/lib/solargraph/pin/common.rb b/lib/solargraph/pin/common.rb index 658c983e2..d52502afe 100644 --- a/lib/solargraph/pin/common.rb +++ b/lib/solargraph/pin/common.rb @@ -18,7 +18,7 @@ module Common # @param value [Pin::Closure] # @return [void] - def closure=(value) + def closure= value @closure = value # remove cached values generated from closure reset_generated! @@ -26,7 +26,10 @@ def closure=(value) # @return [Pin::Closure, nil] def closure - Solargraph.assert_or_log(:closure, "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") unless @closure + unless @closure + Solargraph.assert_or_log(:closure, + "Closure not set on #{self.class} #{name.inspect} from #{source.inspect}") + end @closure end diff --git a/lib/solargraph/pin/compound_statement.rb b/lib/solargraph/pin/compound_statement.rb index 4598d677a..39d9cf2d5 100644 --- a/lib/solargraph/pin/compound_statement.rb +++ b/lib/solargraph/pin/compound_statement.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Pin # A series of statements where if a given statement executes, /all @@ -42,10 +44,8 @@ module Pin class CompoundStatement < Pin::Base attr_reader :node - # @param receiver [Parser::AST::Node, nil] # @param node [Parser::AST::Node, nil] - # @param context [ComplexType, nil] - # @param args [::Array] + # @param [Hash{Symbol => Object}] splat def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin/constant.rb b/lib/solargraph/pin/constant.rb index 94a968e7e..a7696fd1f 100644 --- a/lib/solargraph/pin/constant.rb +++ b/lib/solargraph/pin/constant.rb @@ -33,12 +33,10 @@ def path # @return [ComplexType] def generate_complex_type - tags = docstring.tags(:return).map(&:types).flatten.reject(&:nil?) - if tags.empty? - tags = docstring.tags(:type).map(&:types).flatten.reject(&:nil?) - end + tags = docstring.tags(:return).map(&:types).flatten.compact + tags = docstring.tags(:type).map(&:types).flatten.compact if tags.empty? return ComplexType::UNDEFINED if tags.empty? - ComplexType.try_parse *tags + ComplexType.try_parse(*tags) end end end diff --git a/lib/solargraph/pin/conversions.rb b/lib/solargraph/pin/conversions.rb index 5ad3573f7..1c3aeac99 100644 --- a/lib/solargraph/pin/conversions.rb +++ b/lib/solargraph/pin/conversions.rb @@ -43,8 +43,7 @@ def completion_item data: { path: path, return_type: return_type.tag, - # @sg-ignore flow sensitive typing needs to handle attrs - location: (location ? location.to_hash : nil), + location: location&.to_hash, deprecated: deprecated? } } @@ -73,7 +72,13 @@ def detail # This property is not cached in an instance variable because it can # change when pins get proxied. detail = String.new - detail += "=#{probed? ? '~' : (proxied? ? '^' : '>')} #{return_type.to_s}" unless return_type.undefined? + unless return_type.undefined? + detail += "=#{if probed? + '~' + else + (proxied? ? '^' : '>') + end} #{return_type}" + end detail.strip! return nil if detail.empty? detail @@ -117,7 +122,7 @@ def generate_link # @return [String] def escape_brackets text # text.gsub(/(\<|\>)/, "\\#{$1}") - text.gsub("<", '\<').gsub(">", '\>') + text.gsub('<', '\<').gsub('>', '\>') end end end diff --git a/lib/solargraph/pin/delegated_method.rb b/lib/solargraph/pin/delegated_method.rb index 917e3a4e6..7e17f5042 100644 --- a/lib/solargraph/pin/delegated_method.rb +++ b/lib/solargraph/pin/delegated_method.rb @@ -15,6 +15,7 @@ class DelegatedMethod < Pin::Method # @param receiver [Source::Chain, nil] the source code used to resolve the receiver for this delegated method. # @param name [String, nil] # @param receiver_method_name [String, nil] the method name that will be called on the receiver (defaults to :name). + # @param [Hash{Symbol => Object}] splat def initialize(method: nil, receiver: nil, name: method&.name, receiver_method_name: name, **splat) raise ArgumentError, 'either :method or :receiver is required' if (method && receiver) || (!method && !receiver) # @sg-ignore Need to add nil check here @@ -35,7 +36,6 @@ def location @resolved_method&.send(:location) end - def type_location return super if super @@ -59,7 +59,7 @@ def type_location end # @param api_map [ApiMap] - def resolvable?(api_map) + def resolvable? api_map resolve_method(api_map) !!@resolved_method end @@ -112,10 +112,10 @@ def resolve_method api_map # # @param chain [Source::Chain] # @return [String] - def print_chain(chain) + def print_chain chain out = +'' chain.links.each_with_index do |link, index| - if index > 0 + if index.positive? if Source::Chain::Constant out << '::' unless link.word.start_with?('::') else diff --git a/lib/solargraph/pin/documenting.rb b/lib/solargraph/pin/documenting.rb index cbeaf2a0d..94bd8a551 100644 --- a/lib/solargraph/pin/documenting.rb +++ b/lib/solargraph/pin/documenting.rb @@ -62,7 +62,7 @@ def to_s # @return [String] def to_code - "\n```ruby\n#{Documenting.normalize_indentation(@plaintext)}#{@plaintext.end_with?("\n") ? '' : "\n"}```\n\n" + "\n```ruby\n#{Documenting.normalize_indentation(@plaintext)}#{"\n" unless @plaintext.end_with?("\n")}```\n\n" end # @return [String] @@ -78,7 +78,8 @@ def documentation # line and at least two spaces of indentation. This is a common # convention in Ruby core documentation, e.g., String#split. sections = [DocSection.new(false)] - Documenting.normalize_indentation(Documenting.strip_html_comments(docstring.to_s.gsub("\t", ' '))).lines.each do |l| + Documenting.normalize_indentation(Documenting.strip_html_comments(docstring.to_s.gsub("\t", + ' '))).lines.each do |l| if l.start_with?(' ') # Code block sections.push DocSection.new(true) unless sections.last.code? diff --git a/lib/solargraph/pin/local_variable.rb b/lib/solargraph/pin/local_variable.rb index 7aaab4615..077da21be 100644 --- a/lib/solargraph/pin/local_variable.rb +++ b/lib/solargraph/pin/local_variable.rb @@ -6,7 +6,7 @@ class LocalVariable < BaseVariable # @param api_map [ApiMap] # @return [ComplexType, ComplexType::UniqueType] def probe api_map - if presence_certain? && return_type && return_type&.defined? + if presence_certain? && return_type&.defined? # flow sensitive typing has already figured out this type # has been downcast - use the type it figured out # @sg-ignore flow sensitive typing should support ivars @@ -16,15 +16,15 @@ def probe api_map super end - def combine_with(other, attrs={}) + def combine_with other, attrs = {} # keep this as a parameter - return other.combine_with(self, attrs) if other.is_a?(Parameter) && !self.is_a?(Parameter) + return other.combine_with(self, attrs) if other.is_a?(Parameter) && !is_a?(Parameter) super end def to_rbs - (name || '(anon)') + ' ' + (return_type&.to_rbs || 'untyped') + "#{name || '(anon)'} #{return_type&.to_rbs || 'untyped'}" end end end diff --git a/lib/solargraph/pin/method.rb b/lib/solargraph/pin/method.rb index e6d68c151..f06a563ed 100644 --- a/lib/solargraph/pin/method.rb +++ b/lib/solargraph/pin/method.rb @@ -23,6 +23,7 @@ class Method < Callable # @param signatures [::Array, nil] # @param anon_splat [Boolean] # @param context [ComplexType, ComplexType::UniqueType, nil] + # @param [Hash{Symbol => Object}] splat def initialize visibility: :public, explicit: true, block: :undefined, node: nil, attribute: false, signatures: nil, anon_splat: false, context: nil, **splat super(**splat) @@ -38,7 +39,7 @@ def initialize visibility: :public, explicit: true, block: :undefined, node: nil # @param other [Pin::Method] # @return [::Symbol] - def combine_visibility(other) + def combine_visibility other if dodgy_visibility_source? && !other.dodgy_visibility_source? other.visibility elsif other.dodgy_visibility_source? && !dodgy_visibility_source? @@ -48,16 +49,16 @@ def combine_visibility(other) end end - def combine_with(other, attrs = {}) + def combine_with other, attrs = {} priority_choice = choose_priority(other) return priority_choice unless priority_choice.nil? sigs = combine_signatures(other) - parameters = if sigs.length > 0 - [].freeze - else - choose(other, :parameters).clone.freeze - end + parameters = if sigs.length.positive? + [].freeze + else + choose(other, :parameters).clone.freeze + end new_attrs = { visibility: combine_visibility(other), explicit: explicit? || other.explicit?, @@ -77,7 +78,7 @@ def == other super && other.node == node end - def transform_types(&transform) + def transform_types &transform # @todo 'super' alone should work here I think, but doesn't typecheck at level typed m = super(&transform) m.signatures = m.signatures.map do |sig| @@ -92,14 +93,12 @@ def transform_types(&transform) def reset_generated! super unless signatures.empty? - return_type = nil @block = :undefined - parameters = [] + [] end block&.reset_generated! @signatures&.each(&:reset_generated!) - signature_help = nil - documentation = nil + nil end def all_rooted? @@ -108,7 +107,7 @@ def all_rooted? # @param signature [Pin::Signature] # @return [Pin::Method] - def with_single_signature(signature) + def with_single_signature signature m = proxy signature.return_type m.reset_generated! # @todo populating the single parameters/return_type/block @@ -150,7 +149,7 @@ def return_type # @param parameters [::Array] # @param return_type [ComplexType, nil] # @return [Signature] - def generate_signature(parameters, return_type) + def generate_signature parameters, return_type # @type [Pin::Signature, nil] block = nil yieldparam_tags = docstring.tags(:yieldparam) @@ -172,8 +171,7 @@ def generate_signature(parameters, return_type) comments: p.text, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs to handle attrs - presence: location ? location.range : nil, + presence: location&.range, return_type: ComplexType.try_parse(*p.types), source: source ) @@ -194,7 +192,11 @@ def signatures top_type = generate_complex_type result = [] result.push generate_signature(parameters, top_type) if top_type.defined? - result.concat(overloads.map { |meth| generate_signature(meth.parameters, meth.return_type) }) unless overloads.empty? + unless overloads.empty? + result.concat(overloads.map do |meth| + generate_signature(meth.parameters, meth.return_type) + end) + end result.push generate_signature(parameters, @return_type || ComplexType::UNDEFINED) if result.empty? result end @@ -214,12 +216,18 @@ def detail # change when pins get proxied. detail = String.new detail += if signatures.length > 1 - "(*) " - else - "(#{signatures.first.parameters.map(&:full).join(', ')}) " unless signatures.first.parameters.empty? - end.to_s + '(*) ' + else + "(#{signatures.first.parameters.map(&:full).join(', ')}) " unless signatures.first.parameters.empty? + end.to_s # @sg-ignore Need to add nil check here - detail += "=#{probed? ? '~' : (proxied? ? '^' : '>')} #{return_type.to_s}" unless return_type.undefined? + unless return_type.undefined? + detail += "=#{if probed? + '~' + else + (proxied? ? '^' : '>') + end} #{return_type}" + end detail.strip! return nil if detail.empty? detail @@ -229,7 +237,7 @@ def detail def signature_help @signature_help ||= signatures.map do |sig| { - label: name + '(' + sig.parameters.map(&:full).join(', ') + ')', + label: "#{name}(#{sig.parameters.map(&:full).join(', ')})", documentation: documentation } end @@ -258,7 +266,7 @@ def to_rbs end def path - @path ||= "#{namespace}#{(scope == :instance ? '#' : '.')}#{name}" + @path ||= "#{namespace}#{scope == :instance ? '#' : '.'}#{name}" end # @return [String] @@ -267,11 +275,15 @@ def method_name end def typify api_map - # @sg-ignore Need to add nil check here - logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting" } + logger.debug do + # @sg-ignore Need to add nil check here + "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context.rooted_tags}, return_type=#{return_type.rooted_tags}) - starting" + end decl = super unless decl.undefined? - logger.debug { "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context}) => #{decl.rooted_tags.inspect} - decl found" } + logger.debug do + "Method#typify(self=#{self}, binder=#{binder}, closure=#{closure}, context=#{context}) => #{decl.rooted_tags.inspect} - decl found" + end return decl end type = see_reference(api_map) || typify_from_super(api_map) @@ -289,26 +301,26 @@ def documentation if @documentation.nil? method_docs ||= super || '' param_tags = docstring.tags(:param) - unless param_tags.nil? or param_tags.empty? + unless param_tags.nil? || param_tags.empty? method_docs += "\n\n" unless method_docs.empty? method_docs += "Params:\n" lines = [] param_tags.each do |p| l = "* #{p.name}" - l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty? + l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? || p.types.empty? l += " #{p.text}" lines.push l end method_docs += lines.join("\n") end yieldparam_tags = docstring.tags(:yieldparam) - unless yieldparam_tags.nil? or yieldparam_tags.empty? + unless yieldparam_tags.nil? || yieldparam_tags.empty? method_docs += "\n\n" unless method_docs.empty? method_docs += "Block Params:\n" lines = [] yieldparam_tags.each do |p| l = "* #{p.name}" - l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? or p.types.empty? + l += " [#{escape_brackets(p.types.join(', '))}]" unless p.types.nil? || p.types.empty? l += " #{p.text}" lines.push l end @@ -320,8 +332,8 @@ def documentation method_docs += "Block Returns:\n" lines = [] yieldreturn_tags.each do |r| - l = "*" - l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty? + l = '*' + l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? || r.types.empty? l += " #{r.text}" lines.push l end @@ -333,8 +345,8 @@ def documentation method_docs += "Returns:\n" lines = [] return_tags.each do |r| - l = "*" - l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? or r.types.empty? + l = '*' + l += " [#{escape_brackets(r.types.join(', '))}]" unless r.types.nil? || r.types.empty? l += " #{r.text}" lines.push l end @@ -389,15 +401,14 @@ def overloads comments: tag.docstring.all.to_s, name: name, decl: decl, - # @sg-ignore flow sensitive typing needs to handle attrs - presence: location ? location.range : nil, + presence: location&.range, return_type: param_type_from_name(tag, src.first), source: :overloads ) end, closure: self, return_type: ComplexType.try_parse(*tag.docstring.tags(:return).flat_map(&:types)), - source: :overloads, + source: :overloads ) end @overloads @@ -416,13 +427,13 @@ def resolve_ref_tag api_map return self unless docstring.ref_tags.any? docstring.ref_tags.each do |tag| ref = if tag.owner.to_s.start_with?(/[#.]/) - api_map.get_methods(namespace) - .select { |pin| pin.path.end_with?(tag.owner.to_s) } - .first - else - # @todo Resolve relative namespaces - api_map.get_path_pins(tag.owner.to_s).first - end + api_map.get_methods(namespace) + .select { |pin| pin.path.end_with?(tag.owner.to_s) } + .first + else + # @todo Resolve relative namespaces + api_map.get_path_pins(tag.owner.to_s).first + end next unless ref docstring.add_tag(*ref.docstring.tags(:param)) @@ -438,11 +449,7 @@ def rest_of_stack api_map protected - attr_writer :block - - attr_writer :signature_help - - attr_writer :documentation + attr_writer :block, :signature_help, :documentation, :return_type # @sg-ignore Need to add nil check here def dodgy_visibility_source? @@ -450,22 +457,22 @@ def dodgy_visibility_source? # e.g. activesupport did not understand 'private' markings # inside 'class << self' blocks, but YARD did OK at it # @sg-ignore Need to add nil check here - source == :rbs && scope == :class && type_location&.filename&.include?('generated') && return_type.undefined? || + (source == :rbs && scope == :class && type_location&.filename&.include?('generated') && return_type.undefined?) || # YARD's RBS generator seems to miss a lot of should-be protected instance methods - source == :rbs && scope == :instance && namespace.start_with?('YARD::') || + (source == :rbs && scope == :instance && namespace.start_with?('YARD::')) || # private on attr_readers seems to be broken in Prism's auto-generator script - source == :rbs && scope == :instance && namespace.start_with?('Prism::') || + (source == :rbs && scope == :instance && namespace.start_with?('Prism::')) || # The RBS for the RBS gem itself seems to use private as a # 'is this a public API' concept, more aggressively than the # actual code. Let's respect that and ignore the actual .rb file. - source == :yardoc && scope == :instance && namespace.start_with?('RBS::') + (source == :yardoc && scope == :instance && namespace.start_with?('RBS::')) end private # @param other [Pin::Method] # @return [Array] - def combine_signatures(other) + def combine_signatures other all_undefined = signatures.all? { |sig| !sig.return_type&.defined? } other_all_undefined = other.signatures.all? { |sig| !sig.return_type&.defined? } if all_undefined && !other_all_undefined @@ -497,16 +504,14 @@ def combine_signatures_by_type_arity(*signature_pins) # @param same_type_arity_signatures [Array] # # @return [Array] - def combine_same_type_arity_signatures(same_type_arity_signatures) + def combine_same_type_arity_signatures same_type_arity_signatures # This is an O(n^2) operation, so bail out if n is not small return same_type_arity_signatures if same_type_arity_signatures.length > 10 # @param old_signatures [Array] # @param new_signature [Pin::Signature] same_type_arity_signatures.reduce([]) do |old_signatures, new_signature| - next [new_signature] if old_signatures.empty? - - found_merge = false + next old_signatures + [new_signature] if old_signatures.empty? old_signatures.flat_map do |old_signature| potential_new_signature = old_signature.combine_with(new_signature) @@ -560,7 +565,7 @@ def clean_param name # @param name [String] # # @return [ComplexType] - def param_type_from_name(tag, name) + def param_type_from_name tag, name # @param t [YARD::Tags::Tag] param = tag.tags(:param).select { |t| t.name == name }.first return ComplexType::UNDEFINED unless param @@ -571,7 +576,7 @@ def param_type_from_name(tag, name) def generate_complex_type tags = docstring.tags(:return).map(&:types).flatten.compact return ComplexType::UNDEFINED if tags.empty? - ComplexType.try_parse *tags + ComplexType.try_parse(*tags) end # @param api_map [ApiMap] @@ -635,7 +640,7 @@ def method_body_node return nil if node.nil? return node.children[1].children.last if node.type == :DEFN return node.children[2].children.last if node.type == :DEFS - return node.children[2] if node.type == :def || node.type == :DEFS + return node.children[2] if %i[def DEFS].include?(node.type) return node.children[3] if node.type == :defs nil end @@ -648,7 +653,7 @@ def infer_from_return_nodes api_map has_nil = false return ComplexType::NIL if method_body_node.nil? returns_from_method_body(method_body_node).each do |n| - if n.nil? || [:NIL, :nil].include?(n.type) + if n.nil? || %i[NIL nil].include?(n.type) has_nil = true next end @@ -688,12 +693,12 @@ def infer_from_iv api_map # # @param name [String] # @return [::Array(String, ::Symbol)] - def parse_overload_param(name) + def parse_overload_param name # @todo this needs to handle mandatory vs not args, kwargs, blocks, etc if name.start_with?('**') - [name[2..-1], :kwrestarg] + [name[2..], :kwrestarg] elsif name.start_with?('*') - [name[1..-1], :restarg] + [name[1..], :restarg] else [name, :arg] end @@ -711,10 +716,6 @@ def concat_example_tags .join("\n") .concat("```\n") end - - protected - - attr_writer :return_type end end end diff --git a/lib/solargraph/pin/namespace.rb b/lib/solargraph/pin/namespace.rb index f41a7ae2b..55bb52b0e 100644 --- a/lib/solargraph/pin/namespace.rb +++ b/lib/solargraph/pin/namespace.rb @@ -20,13 +20,14 @@ class Namespace < Closure # @param visibility [::Symbol] :public or :private # @param gates [::Array] # @param name [String] + # @param [Hash{Symbol => Object}] splat def initialize type: :class, visibility: :public, gates: [''], name: '', **splat # super(location, namespace, name, comments) super(**splat, name: name) @type = type @visibility = visibility if name.start_with?('::') - name = name[2..-1] || '' + name = name[2..] || '' @closure = Solargraph::Pin::ROOT_PIN end @open_gates = gates @@ -36,11 +37,11 @@ def initialize type: :class, visibility: :public, gates: [''], name: '', **splat parts = name.split('::') name = parts.pop closure_name = if [Solargraph::Pin::ROOT_PIN, nil].include?(closure) - '' - else - # @sg-ignore Need to add nil check here - closure.full_context.namespace + '::' - end + '' + else + # @sg-ignore Need to add nil check here + "#{closure.full_context.namespace}::" + end closure_name += parts.join('::') @closure = Pin::Namespace.new(name: closure_name, gates: [parts.join('::')], source: :namespace) @context = nil @@ -55,7 +56,7 @@ def reset_generated! end def to_rbs - "#{@type.to_s} #{return_type.all_params.first.to_rbs}#{rbs_generics}".strip + "#{@type} #{return_type.all_params.first.to_rbs}#{rbs_generics}".strip end def inner_desc @@ -97,7 +98,7 @@ def path end def return_type - @return_type ||= ComplexType.try_parse( (type == :class ? '::Class' : '::Module') + "<::#{path}>") + @return_type ||= ComplexType.try_parse((type == :class ? '::Class' : '::Module') + "<::#{path}>") end # @return [Array] @@ -111,10 +112,10 @@ def typify api_map def gates @gates ||= if path.empty? - @open_gates - else - [path] + @open_gates - end + @open_gates + else + [path] + @open_gates + end end end end diff --git a/lib/solargraph/pin/parameter.rb b/lib/solargraph/pin/parameter.rb index 05a8cb490..972753f7d 100644 --- a/lib/solargraph/pin/parameter.rb +++ b/lib/solargraph/pin/parameter.rb @@ -15,6 +15,7 @@ class Parameter < LocalVariable # @param decl [::Symbol] :arg, :optarg, :kwarg, :kwoptarg, :restarg, :kwrestarg, :block, :blockarg # @param asgn_code [String, nil] + # @param [Hash{Symbol => Object}] splat def initialize decl: :arg, asgn_code: nil, **splat super(**splat) @asgn_code = asgn_code @@ -29,7 +30,7 @@ def location super || closure&.type_location end - def combine_with(other, attrs={}) + def combine_with other, attrs = {} # Parameters can be combined with local variables new_attrs = if other.is_a?(Parameter) { @@ -45,7 +46,7 @@ def combine_with(other, attrs={}) super(other, new_attrs.merge(attrs)) end - def combine_return_type(other) + def combine_return_type other out = super if out&.undefined? # allow our return_type method to provide a better type @@ -56,12 +57,12 @@ def combine_return_type(other) end def keyword? - [:kwarg, :kwoptarg].include?(decl) + %i[kwarg kwoptarg].include?(decl) end def kwrestarg? # @sg-ignore flow sensitive typing needs to handle attrs - decl == :kwrestarg || (assignment && [:HASH, :hash].include?(assignment.type)) + decl == :kwrestarg || (assignment && %i[HASH hash].include?(assignment.type)) end def needs_consistent_name? @@ -70,21 +71,21 @@ def needs_consistent_name? # @return [String] def arity_decl - name = (self.name || '(anon)') - type = (return_type&.to_rbs || 'untyped') + name = self.name || '(anon)' + return_type&.to_rbs || 'untyped' case decl when :arg - "" + '' when :optarg - "?" + '?' when :kwarg "#{name}:" when :kwoptarg "?#{name}:" when :restarg - "*" + '*' when :kwrestarg - "**" + '**' else "(unknown decl: #{decl})" end @@ -112,11 +113,11 @@ def positional? end def rest? - decl == :restarg || decl == :kwrestarg + %i[restarg kwrestarg].include?(decl) end def block? - [:block, :blockarg].include?(decl) + %i[block blockarg].include?(decl) end def to_rbs @@ -177,14 +178,15 @@ def return_type if @return_type.nil? @return_type = ComplexType::UNDEFINED found = param_tag - @return_type = ComplexType.try_parse(*found.types) unless found.nil? or found.types.nil? + @return_type = ComplexType.try_parse(*found.types) unless found.nil? || found.types.nil? # @sg-ignore flow sensitive typing should be able to handle redefinition if @return_type.undefined? - if decl == :restarg + case decl + when :restarg @return_type = ComplexType.try_parse('::Array') - elsif decl == :kwrestarg + when :kwrestarg @return_type = ComplexType.try_parse('::Hash') - elsif decl == :blockarg + when :blockarg @return_type = ComplexType.try_parse('::Proc') end end @@ -217,7 +219,7 @@ def typify api_map # @param atype [ComplexType] # @param api_map [ApiMap] - def compatible_arg?(atype, api_map) + def compatible_arg? atype, api_map # make sure we get types from up the method # inheritance chain if we don't have them on this pin ptype = typify api_map @@ -226,7 +228,7 @@ def compatible_arg?(atype, api_map) return true if atype.conforms_to?(api_map, ptype, :method_call, - [:allow_empty_params, :allow_undefined]) + %i[allow_empty_params allow_undefined]) ptype.generic? end @@ -259,9 +261,7 @@ def param_tag # @return [ComplexType] def typify_block_param api_map block_pin = closure - if block_pin.is_a?(Pin::Block) && block_pin.receiver && index - return block_pin.typify_parameters(api_map)[index] - end + return block_pin.typify_parameters(api_map)[index] if block_pin.is_a?(Pin::Block) && block_pin.receiver && index ComplexType::UNDEFINED end @@ -279,11 +279,14 @@ def typify_method_param api_map found = p break end - if found.nil? and !index.nil? - found = params[index] if params[index] && (params[index].name.nil? || params[index].name.empty?) + if found.nil? && !index.nil? && params[index] && (params[index].name.nil? || params[index].name.empty?) + found = params[index] + end + unless found.nil? || found.types.nil? + return ComplexType.try_parse(*found.types).qualify(api_map, + # @sg-ignore Need to add nil check here + *meth.closure.gates) end - # @sg-ignore Need to add nil check here - return ComplexType.try_parse(*found.types).qualify(api_map, *meth.closure.gates) unless found.nil? || found.types.nil? end ComplexType::UNDEFINED end @@ -326,8 +329,6 @@ def resolve_reference ref, api_map, skip pins.each do |pin| params = pin.docstring.tags(:param) return params unless params.empty? - end - pins.each do |pin| params = see_reference(pin.docstring, api_map, skip) return params unless params.empty? end diff --git a/lib/solargraph/pin/proxy_type.rb b/lib/solargraph/pin/proxy_type.rb index e856c8833..fd37bab85 100644 --- a/lib/solargraph/pin/proxy_type.rb +++ b/lib/solargraph/pin/proxy_type.rb @@ -7,6 +7,7 @@ class ProxyType < Base # @param gates [Array, nil] Namespaces to try while resolving non-rooted types # @param binder [ComplexType, ComplexType::UniqueType, nil] # @param gates [Array, nil] + # @param [Hash{Symbol => Object}] splat def initialize return_type: ComplexType::UNDEFINED, binder: nil, gates: nil, **splat super(**splat) @gates = gates @@ -22,6 +23,7 @@ def context # @param closure [Pin::Namespace, nil] Used as the closure for this pin # @param binder [ComplexType, ComplexType::UniqueType, nil] # @return [ProxyType] + # @param [Hash{Symbol => Object}] kwargs def self.anonymous context, closure: nil, binder: nil, **kwargs unless closure parts = context.namespace.split('::') diff --git a/lib/solargraph/pin/reference.rb b/lib/solargraph/pin/reference.rb index 13e603d6e..fc54db021 100644 --- a/lib/solargraph/pin/reference.rb +++ b/lib/solargraph/pin/reference.rb @@ -12,7 +12,24 @@ class Reference < Base attr_reader :generic_values + # A Reference is a pin that associates a type with another type. + # The existing type is marked as the closure. The name of the + # type we're associating with it is the 'name' field, and + # subtypes are in the 'generic_values' field. + # + # These pins are a little different - the name is a rooted name, + # which may be relative or absolute, preceded with ::, not a + # fully qualified namespace, which is implicitly in the root + # namespace and is never preceded by ::. + # + # @todo can the above be represented in a less subtle way? + # @todo consider refactoring so that we can replicate more + # complex types like Hash{String => Integer} and has both key + # types and subtypes. + # + # @param name [String] rooted name of the referenced type # @param generic_values [Array] + # @param [Hash{Symbol => Object}] splat def initialize generic_values: [], **splat super(**splat) @generic_values = generic_values diff --git a/lib/solargraph/pin/search.rb b/lib/solargraph/pin/search.rb index 67bd74d24..54e4d5bc5 100644 --- a/lib/solargraph/pin/search.rb +++ b/lib/solargraph/pin/search.rb @@ -46,7 +46,7 @@ def do_query # @param b [self] # @sg-ignore https://github.com/castwide/solargraph/pull/1050 .sort { |a, b| b.match <=> a.match } - .map(&:pin) + .map(&:pin) end # @param str1 [String] @@ -54,7 +54,7 @@ def do_query # # @return [Float] def fuzzy_string_match str1, str2 - return 1.0 + (str2.length.to_f / str1.length.to_f) if str1.downcase.include?(str2.downcase) + return 1.0 + (str2.length.to_f / str1.length) if str1.downcase.include?(str2.downcase) JaroWinkler.similarity(str1, str2, ignore_case: true) end end diff --git a/lib/solargraph/pin/signature.rb b/lib/solargraph/pin/signature.rb index 0a6dbbafb..221682ccd 100644 --- a/lib/solargraph/pin/signature.rb +++ b/lib/solargraph/pin/signature.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph module Pin class Signature < Callable @@ -5,10 +7,6 @@ class Signature < Callable # to the method pin attr_writer :closure - def initialize **splat - super(**splat) - end - def generics # @type [Array<::String, nil>] @generics ||= [].freeze @@ -18,8 +16,6 @@ def identity @identity ||= "signature#{object_id}" end - attr_writer :closure - # @ sg-ignore need boolish support for ? methods def dodgy_return_type_source? super || closure&.dodgy_return_type_source? @@ -49,16 +45,15 @@ def typify api_map method_stack = closure.rest_of_stack api_map logger.debug { "Signature#typify(self=#{self}) - method_stack: #{method_stack}" } method_stack.each do |pin| - sig = pin.signatures.find { |s| s.arity == self.arity } + sig = pin.signatures.find { |s| s.arity == arity } next unless sig # @sg-ignore Need to add nil check here - unless sig.return_type.undefined? - # @sg-ignore Need to add nil check here - qualified = sig.return_type.qualify(api_map, closure.namespace) - # @sg-ignore Need to add nil check here - logger.debug { "Signature#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } - return qualified - end + next if sig.return_type.undefined? + # @sg-ignore Need to add nil check here + qualified = sig.return_type.qualify(api_map, closure.namespace) + # @sg-ignore Need to add nil check here + logger.debug { "Signature#typify(self=#{self}) => #{qualified.rooted_tags.inspect}" } + return qualified end out = super logger.debug { "Signature#typify(self=#{self}) => #{out}" } diff --git a/lib/solargraph/pin/symbol.rb b/lib/solargraph/pin/symbol.rb index 18178e9b9..f28fb2a71 100644 --- a/lib/solargraph/pin/symbol.rb +++ b/lib/solargraph/pin/symbol.rb @@ -5,6 +5,7 @@ module Pin class Symbol < Base # @param location [Solargraph::Location, nil] # @param name [String] + # @param [Hash{Symbol => Object}] kwargs def initialize(location, name, **kwargs) # @sg-ignore "Unrecognized keyword argument kwargs to Solargraph::Pin::Base#initialize" super(location: location, name: name, **kwargs) diff --git a/lib/solargraph/pin/until.rb b/lib/solargraph/pin/until.rb index 7e050fea6..f6da568c6 100644 --- a/lib/solargraph/pin/until.rb +++ b/lib/solargraph/pin/until.rb @@ -5,10 +5,8 @@ module Pin class Until < CompoundStatement include Breakable - # @param receiver [Parser::AST::Node, nil] # @param node [Parser::AST::Node, nil] - # @param context [ComplexType, nil] - # @param args [::Array] + # @param [Hash{Symbol => Object}] splat def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin/while.rb b/lib/solargraph/pin/while.rb index ac8c31c97..f10616891 100644 --- a/lib/solargraph/pin/while.rb +++ b/lib/solargraph/pin/while.rb @@ -5,10 +5,8 @@ module Pin class While < CompoundStatement include Breakable - # @param receiver [Parser::AST::Node, nil] # @param node [Parser::AST::Node, nil] - # @param context [ComplexType, nil] - # @param args [::Array] + # @param [Hash{Symbol => Object}] splat def initialize node: nil, **splat super(**splat) @node = node diff --git a/lib/solargraph/pin_cache.rb b/lib/solargraph/pin_cache.rb index 11c825787..886d55838 100644 --- a/lib/solargraph/pin_cache.rb +++ b/lib/solargraph/pin_cache.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'rbs' require 'rubygems' @@ -408,6 +410,7 @@ def exist? *path # @return [void] # @param path_segments [Array] + # @param out [StringIO, IO, nil] def uncache_by_prefix *path_segments, out: nil path = File.join(*path_segments) glob = "#{path}*" @@ -447,7 +450,7 @@ def uncache *path_segments, out: nil path = File.join(*path_segments) if File.exist?(path) FileUtils.rm_rf path, secure: true - out.puts "Clearing pin cache in #{path}" unless out.nil? + out&.puts "Clearing pin cache in #{path}" else out&.puts "Pin cache file #{path} does not exist" end @@ -459,11 +462,11 @@ def uncache *path_segments, out: nil def uncache_by_prefix *path_segments, out: nil path = File.join(*path_segments) glob = "#{path}*" - out.puts "Clearing pin cache in #{glob}" unless out.nil? + out&.puts "Clearing pin cache in #{glob}" Dir.glob(glob).each do |file| next unless File.file?(file) FileUtils.rm_rf file, secure: true - out.puts "Clearing pin cache in #{file}" unless out.nil? + out&.puts "Clearing pin cache in #{file}" end end @@ -538,40 +541,34 @@ def yard_gem_path gemspec # @param gemspec [Gem::Specification] # @return [Array, nil] - def deserialize_yard_gem(gemspec) + def deserialize_yard_gem gemspec load(yard_gem_path(gemspec)) end # @param gemspec [Gem::Specification] # @param pins [Array] # @return [void] - def serialize_yard_gem(gemspec, pins) + def serialize_yard_gem gemspec, pins save(yard_gem_path(gemspec), pins) end - # @param gemspec [Gem::Specification] - # @return [Boolean] - def has_yard?(gemspec) - exist?(yard_gem_path(gemspec)) - end - # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [String] - def rbs_collection_path(gemspec, hash) + def rbs_collection_path gemspec, hash File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser") end # @param gemspec [Gem::Specification] # @return [String] - def rbs_collection_path_prefix(gemspec) + def rbs_collection_path_prefix gemspec File.join(work_dir, 'rbs', "#{gemspec.name}-#{gemspec.version}-") end # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [Array, nil] - def deserialize_rbs_collection_gem(gemspec, hash) + def deserialize_rbs_collection_gem gemspec, hash load(rbs_collection_path(gemspec, hash)) end @@ -579,20 +576,20 @@ def deserialize_rbs_collection_gem(gemspec, hash) # @param hash [String, nil] # @param pins [Array]n # @return [void] - def serialize_rbs_collection_gem(gemspec, hash, pins) + def serialize_rbs_collection_gem gemspec, hash, pins save(rbs_collection_path(gemspec, hash), pins) end # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [String] - def combined_path(gemspec, hash) + def combined_path gemspec, hash File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-#{hash || 0}.ser") end # @param gemspec [Gem::Specification] # @return [String] - def combined_path_prefix(gemspec) + def combined_path_prefix gemspec File.join(work_dir, 'combined', "#{gemspec.name}-#{gemspec.version}-") end @@ -600,7 +597,7 @@ def combined_path_prefix(gemspec) # @param hash [String, nil] # @param pins [Array] # @return [void] - def serialize_combined_gem(gemspec, hash, pins) + def serialize_combined_gem gemspec, hash, pins save(combined_path(gemspec, hash), pins) end @@ -614,7 +611,7 @@ def deserialize_combined_gem gemspec, hash # @param gemspec [Gem::Specification] # @param hash [String, nil] # @return [Boolean] - def has_rbs_collection?(gemspec, hash) + def has_rbs_collection? gemspec, hash exist?(rbs_collection_path(gemspec, hash)) end diff --git a/lib/solargraph/position.rb b/lib/solargraph/position.rb index 53c7b61ba..cc20f9607 100644 --- a/lib/solargraph/position.rb +++ b/lib/solargraph/position.rb @@ -21,12 +21,8 @@ def initialize line, character @character = character end - protected def equality_fields - [line, character] - end - # @param other [Position] - def <=>(other) + def <=> other return nil unless other.is_a?(Position) if line == other.line character <=> other.character @@ -57,7 +53,22 @@ def inspect # @return [Integer] def self.to_offset text, position return 0 if text.empty? - text.lines[0...position.line].sum(&:length) + position.character + + newline_index = -1 + line = -1 + last_line_index = 0 + + # @sg-ignore flow sensitive typing should be able to handle redefinition + while (newline_index = text.index("\n", newline_index + 1)) && line <= position.line + line += 1 + break if line == position.line + + last_line_index = newline_index + end + + last_line_index += 1 if position.line.positive? + # @sg-ignore flow sensitive typing should be able to handle redefinition + last_line_index + position.character end # Get a numeric offset for the specified text and a position identified @@ -73,25 +84,26 @@ def self.line_char_to_offset text, line, character # Get a position for the specified text and offset. # + # @raise [InvalidOffsetError] if the offset is outside the text range + # # @param text [String] # @param offset [Integer] # @return [Position] def self.from_offset text, offset + raise InvalidOffsetError if offset > text.length + cursor = 0 line = 0 - # @type [Integer, nil] - character = nil - text.lines.each do |l| - line_length = l.length - char_length = l.chomp.length - if cursor + char_length >= offset - character = offset - cursor - break - end - cursor += line_length + character = offset + newline_index = -1 + + # @sg-ignore flow sensitive typing should be able to handle redefinition + while (newline_index = text.index("\n", newline_index + 1)) && newline_index < offset line += 1 + # @sg-ignore flow sensitive typing should be able to handle redefinition + character = offset - newline_index - 1 end - character = 0 if character.nil? and (cursor - offset).between?(0, 1) + character = 0 if character.nil? && (cursor - offset).between?(0, 1) raise InvalidOffsetError if character.nil? # @sg-ignore flow sensitive typing needs to handle 'raise if' Position.new(line, character) @@ -114,5 +126,11 @@ def == other return false unless other.is_a?(Position) line == other.line and character == other.character end + + protected + + def equality_fields + [line, character] + end end end diff --git a/lib/solargraph/range.rb b/lib/solargraph/range.rb index 50bec73d8..e1ed89592 100644 --- a/lib/solargraph/range.rb +++ b/lib/solargraph/range.rb @@ -19,12 +19,8 @@ def initialize start, ending @ending = ending end - protected def equality_fields - [start, ending] - end - # @param other [BasicObject] - def <=>(other) + def <=> other return nil unless other.is_a?(Range) if start == other.start ending <=> other.ending @@ -36,7 +32,7 @@ def <=>(other) # Get a hash of the range. This representation is suitable for use in # the language server protocol. # - # @return [Hash] + # @return [Hash{Symbol => Position}] def to_hash { start: start.to_hash, @@ -86,9 +82,8 @@ def self.from_to l1, c1, l2, c2 # @param node [::Parser::AST::Node] # @return [Range, nil] def self.from_node node - if node&.loc && node.loc.expression - from_expr(node.loc.expression) - end + return unless node&.loc&.expression + from_expr(node.loc.expression) end # Get a range from a Parser range, usually found in @@ -108,5 +103,11 @@ def == other def inspect "#<#{self.class} #{start.inspect} to #{ending.inspect}>" end + + protected + + def equality_fields + [start, ending] + end end end diff --git a/lib/solargraph/rbs_map.rb b/lib/solargraph/rbs_map.rb index a04568587..66aff1288 100644 --- a/lib/solargraph/rbs_map.rb +++ b/lib/solargraph/rbs_map.rb @@ -16,11 +16,7 @@ class RbsMap # @type [Hash{String => RbsMap}] @@rbs_maps_hash = {} - attr_reader :library - - attr_reader :rbs_collection_paths - - attr_reader :rbs_collection_config_path + attr_reader :library, :rbs_collection_paths, :rbs_collection_config_path # @param library [String] # @param version [String, nil] @@ -72,7 +68,7 @@ def loader def cache_key return CACHE_KEY_UNRESOLVED unless resolved? - @hextdigest ||= begin + @cache_key ||= begin # @type [String, nil] data = nil # @type gem_config [nil, Hash{String => Hash{String => String}}] @@ -145,7 +141,7 @@ def pins out: $stderr # @return [generic, nil] def path_pin path, klass = Pin::Base pin = pins.find { |p| p.path == path } - pin if pin&.is_a?(klass) + pin if pin.is_a?(klass) end # @param path [String] @@ -176,11 +172,6 @@ def self.load library private - # @return [RBS::EnvironmentLoader] - def loader - @loader ||= RBS::EnvironmentLoader.new(core_root: nil, repository: repository) - end - # @return [Conversions] def conversions @conversions ||= Conversions.new(loader: loader) diff --git a/lib/solargraph/rbs_map/conversions.rb b/lib/solargraph/rbs_map/conversions.rb index b958895fb..b6295040d 100644 --- a/lib/solargraph/rbs_map/conversions.rb +++ b/lib/solargraph/rbs_map/conversions.rb @@ -23,7 +23,7 @@ def initialize visibility = :public end # @param loader [RBS::EnvironmentLoader] - def initialize(loader:) + def initialize loader: @loader = loader @pins = [] load_environment_to_pins(loader) @@ -43,12 +43,14 @@ def type_aliases end # @param loader [RBS::EnvironmentLoader] + # # @return [void] - def load_environment_to_pins(loader) + def load_environment_to_pins loader environment = RBS::Environment.from_loader(loader).resolve_type_names - cursor = pins.length if environment.declarations.empty? - Solargraph.logger.info "No RBS declarations found in environment for core_root #{loader.core_root.inspect}, libraries #{loader.libs} and directories #{loader.dirs}" + Solargraph.logger.info 'No RBS declarations found in environment for core_root ' \ + "#{loader.core_root.inspect}, libraries #{loader.libs} and " \ + "directories #{loader.dirs}" return end environment.declarations.each { |decl| convert_decl_to_pin(decl, Solargraph::Pin::ROOT_PIN) } @@ -60,22 +62,57 @@ def load_environment_to_pins(loader) def convert_decl_to_pin decl, closure case decl when RBS::AST::Declarations::Class + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on class #{decl.inspect}") + end class_decl_to_pin decl when RBS::AST::Declarations::Interface + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on interface #{decl.inspect}") + end # STDERR.puts "Skipping interface #{decl.name.relative!}" - interface_decl_to_pin decl, closure + interface_decl_to_pin decl when RBS::AST::Declarations::TypeAlias + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + Solargraph.assert_or_log(:rbs_closure, + # @sg-ignore flow sensitive typing should support case/when + "Ignoring closure #{closure.inspect} on alias type name #{decl.name}") + end # @sg-ignore flow sensitive typing should support case/when type_aliases[decl.name.to_s] = decl when RBS::AST::Declarations::Module + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + Solargraph.assert_or_log(:rbs_closure, + # @sg-ignore flow sensitive typing should support case/when + "Ignoring closure #{closure.inspect} on alias type name #{decl.name}") + end module_decl_to_pin decl when RBS::AST::Declarations::Constant + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.name.absolute? + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on constant #{decl.inspect}") + end constant_decl_to_pin decl when RBS::AST::Declarations::ClassAlias + # @sg-ignore flow sensitive typing should support case/when + unless closure.name == '' || decl.new_name.absolute? + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on class alias #{decl.inspect}") + end class_alias_decl_to_pin decl when RBS::AST::Declarations::ModuleAlias + unless closure.name == '' + Solargraph.assert_or_log(:rbs_closure, + "Ignoring closure #{closure.inspect} on module alias #{decl.inspect}") + end module_alias_decl_to_pin decl when RBS::AST::Declarations::Global + unless closure.name == '' + Solargraph.assert_or_log(:rbs_closure, "Ignoring closure #{closure.inspect} on global decl #{decl.inspect}") + end global_decl_to_pin decl else Solargraph.logger.warn "Skipping declaration #{decl.class}" @@ -86,7 +123,61 @@ def convert_decl_to_pin decl, closure # @param module_pin [Pin::Namespace] # @return [void] def convert_self_types_to_pins decl, module_pin - decl.self_types.each { |self_type| context = convert_self_type_to_pins(self_type, module_pin) } + decl.self_types.each { |self_type| convert_self_type_to_pins(self_type, module_pin) } + end + + # @type [Hash{String => String}] + RBS_TO_CLASS = { + 'bool' => 'Boolean', + 'string' => 'String', + 'int' => 'Integer' + }.freeze + private_constant :RBS_TO_CLASS + + # rooted names (namespaces) use the prefix of :: when they are + # relative to the root namespace, or not if they are relative to + # the current namespace. + # + # @param type_name [RBS::TypeName] + # + # @return [String] + def rooted_name type_name + name = type_name.to_s + RBS_TO_CLASS.fetch(name, name) + end + + # fqns names are implicitly fully qualified - they are relative + # to the root namespace and are not prefixed with :: + # + # @param type_name [RBS::TypeName] + # + # @return [String] + def fqns type_name + unless type_name.absolute? + Solargraph.assert_or_log(:rbs_fqns, "Received unexpected unqualified type name: #{type_name}") + end + ns = type_name.relative!.to_s + RBS_TO_CLASS.fetch(ns, ns) + end + + # @param type_name [RBS::TypeName] + # @param type_args [Enumerable] + # @return [ComplexType::UniqueType] + def build_type type_name, type_args = [] + # we use .absolute? below to tell the type object what to + # expect + rbs_name = type_name.relative!.to_s + base = RBS_TO_CLASS.fetch(rbs_name, rbs_name) + + params = type_args.map { |a| other_type_to_type(a) } + # tuples have their own class and are handled in other_type_to_type + if base == 'Hash' && params.length == 2 + ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: type_name.absolute?, + parameters_type: :hash) + else + ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: type_name.absolute?, + parameters_type: :list) + end end # @param decl [RBS::AST::Declarations::Module::Self] @@ -94,9 +185,9 @@ def convert_self_types_to_pins decl, module_pin # @return [void] def convert_self_type_to_pins decl, closure type = build_type(decl.name, decl.args) - generic_values = type.all_params.map(&:to_s) + generic_values = type.all_params.map(&:rooted_tags) include_pin = Solargraph::Pin::Reference::Include.new( - name: type.rooted_name, + name: type.name, type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, @@ -165,18 +256,31 @@ def convert_member_to_pin member, closure, context context end + # Pull the name of type variables for a generic - not the + # values, the names (e.g., T, U, V). As such, "rooting" isn't a + # thing, these are all in the global namespace. + # + # @param decl [RBS::AST::Declarations::Class, RBS::AST::Declarations::Interface, + # RBS::AST::Declarations::Module, RBS::MethodType] + # + # @return [Array] + def type_parameter_names decl + decl.type_params.map(&:name).map(&:to_s) + end + # @param decl [RBS::AST::Declarations::Class] # @return [void] def class_decl_to_pin decl - generics = decl.type_params.map(&:name).map(&:to_s) + # @type [Hash{String => ComplexType, ComplexType::UniqueType}] generic_defaults = {} decl.type_params.each do |param| - if param.default_type - tag = other_type_to_tag param.default_type - generic_defaults[param.name.to_s] = ComplexType.parse(tag).force_rooted - end + generic_defaults[param.name.to_s] = other_type_to_type param.default_type if param.default_type end - class_name = decl.name.relative!.to_s + + class_name = fqns(decl.name) + + generics = type_parameter_names(decl) + class_pin = Solargraph::Pin::Namespace.new( type: :class, name: class_name, @@ -193,13 +297,12 @@ def class_decl_to_pin decl pins.push class_pin if decl.super_class type = build_type(decl.super_class.name, decl.super_class.args) - generic_values = type.all_params.map(&:to_s) - superclass_name = decl.super_class.name.to_s + generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Superclass.new( type_location: location_decl_to_pin_location(decl.super_class.location), closure: class_pin, generic_values: generic_values, - name: superclass_name, + name: type.rooted_name, # reference pins use rooted names source: :rbs ) end @@ -208,16 +311,15 @@ def class_decl_to_pin decl end # @param decl [RBS::AST::Declarations::Interface] - # @param closure [Pin::Closure] # @return [void] - def interface_decl_to_pin decl, closure + def interface_decl_to_pin decl class_pin = Solargraph::Pin::Namespace.new( type: :module, type_location: location_decl_to_pin_location(decl.location), - name: decl.name.relative!.to_s, + name: fqns(decl.name), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, - generics: decl.type_params.map(&:name).map(&:to_s), + generics: type_parameter_names(decl), # HACK: Using :hidden to keep interfaces from appearing in # autocompletion visibility: :hidden, @@ -233,11 +335,11 @@ def interface_decl_to_pin decl, closure def module_decl_to_pin decl module_pin = Solargraph::Pin::Namespace.new( type: :module, - name: decl.name.relative!.to_s, + name: fqns(decl.name), type_location: location_decl_to_pin_location(decl.location), closure: Solargraph::Pin::ROOT_PIN, comments: decl.comment&.string, - generics: decl.type_params.map(&:name).map(&:to_s), + generics: type_parameter_names(decl), source: :rbs ) pins.push module_pin @@ -249,32 +351,35 @@ def module_decl_to_pin decl add_mixins decl, module_pin.closure end - # @param name [String] - # @param tag [String] + # @param fqns [String] + # @param type [ComplexType, ComplexType::UniqueType] # @param comments [String, nil] - # @param decl [RBS::AST::Declarations::ClassAlias, RBS::AST::Declarations::Constant, RBS::AST::Declarations::ModuleAlias] - # @param base [String, nil] Optional conversion of tag to base + # @param decl [RBS::AST::Declarations::ClassAlias, + # RBS::AST::Declarations::Constant, + # RBS::AST::Declarations::ModuleAlias] + # @param base [String, nil] Optional conversion of tag to + # base - valid values are Class and Module # # @return [Solargraph::Pin::Constant] - def create_constant(name, tag, comments, decl, base = nil) - parts = name.split('::') + def create_constant fqns, type, comments, decl, base = nil + parts = fqns.split('::') if parts.length > 1 - name = parts.last + fqns = parts.last # @sg-ignore Need to add nil check here closure = pins.select { |pin| pin && pin.path == parts[0..-2].join('::') }.first else - name = parts.first + fqns = parts.first closure = Solargraph::Pin::ROOT_PIN end constant_pin = Solargraph::Pin::Constant.new( - name: name, + name: fqns, closure: closure, type_location: location_decl_to_pin_location(decl.location), comments: comments, source: :rbs ) - tag = "#{base}<#{tag}>" if base - rooted_tag = ComplexType.parse(tag).force_rooted.rooted_tags + rooted_tag = type.rooted_tags + rooted_tag = "#{base}<#{rooted_tag}>" if base constant_pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) constant_pin end @@ -283,27 +388,27 @@ def create_constant(name, tag, comments, decl, base = nil) # @return [void] def class_alias_decl_to_pin decl # See https://www.rubydoc.info/gems/rbs/3.4.3/RBS/AST/Declarations/ClassAlias - new_name = decl.new_name.relative!.to_s - old_name = decl.old_name.relative!.to_s - - pins.push create_constant(new_name, old_name, decl.comment&.string, decl, 'Class') + new_name = fqns(decl.new_name) + old_type = build_type(decl.old_name) + pins.push create_constant(new_name, old_type, decl.comment&.string, decl, '::Class') end # @param decl [RBS::AST::Declarations::ModuleAlias] # @return [void] def module_alias_decl_to_pin decl # See https://www.rubydoc.info/gems/rbs/3.4.3/RBS/AST/Declarations/ModuleAlias - new_name = decl.new_name.relative!.to_s - old_name = decl.old_name.relative!.to_s + new_name = fqns(decl.new_name) + old_type = build_type(decl.old_name) - pins.push create_constant(new_name, old_name, decl.comment&.string, decl, 'Module') + pins.push create_constant(new_name, old_type, decl.comment&.string, decl, '::Module') end # @param decl [RBS::AST::Declarations::Constant] # @return [void] def constant_decl_to_pin decl - tag = other_type_to_tag(decl.type) - pins.push create_constant(decl.name.relative!.to_s, tag, decl.comment&.string, decl) + target_type = other_type_to_type(decl.type) + constant_name = fqns(decl.name) + pins.push create_constant(constant_name, target_type, decl.comment&.string, decl) end # @param decl [RBS::AST::Declarations::Global] @@ -318,12 +423,11 @@ def global_decl_to_pin decl type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end - # Visibility overrides that will allow the Solargraph project # and plugins to pass typechecking using SOLARGRAPH_ASSERTS=on, # so that we can detect any regressions/issues elsewhere in the @@ -339,41 +443,45 @@ def global_decl_to_pin decl # allow that to be extended via .solargraph.yml # @type [Hash{Array(String, Symbol, String) => Symbol} VISIBILITY_OVERRIDE = { - ["Rails::Engine", :instance, "run_tasks_blocks"] => :protected, + ['Rails::Engine', :instance, 'run_tasks_blocks'] => :protected, # Should have been marked as both instance and class method in module -e.g., 'module_function' - ["Kernel", :instance, "pretty_inspect"] => :private, + ['Kernel', :instance, 'pretty_inspect'] => :private, # marked incorrectly in RBS - ["WEBrick::HTTPUtils::FormData", :instance, "next_data"] => :protected, - ["Rails::Command", :class, "command_type"] => :private, - ["Rails::Command", :class, "lookup_paths"] => :private, - ["Rails::Command", :class, "file_lookup_paths"] => :private, - ["Rails::Railtie", :instance, "run_console_blocks"] => :protected, - ["Rails::Railtie", :instance, "run_generators_blocks"] => :protected, - ["Rails::Railtie", :instance, "run_runner_blocks"] => :protected, - ["Rails::Railtie", :instance, "run_tasks_blocks"] => :protected, - ["ActionController::Base", :instance, "_protected_ivars"] => :private, - ["ActionView::Template", :instance, "method_name"] => :public, - ["Module", :instance, "ruby2_keywords"] => :private, - ["Nokogiri::XML::Node", :instance, "coerce"] => :protected, - ["Nokogiri::XML::Document", :class, "empty_doc?"] => :private, - ["Nokogiri::Decorators::Slop", :instance, "respond_to_missing?"] => :public, - ["RuboCop::Cop::RangeHelp", :instance, "source_range"] => :private, - ["AST::Node", :instance, "original_dup"] => :private, - ["Rainbow::Presenter", :instance, "wrap_with_sgr"] => :private, - } - - # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, RBS::AST::Members::AttrWriter, RBS::AST::Members::AttrAccessor] + ['WEBrick::HTTPUtils::FormData', :instance, 'next_data'] => :protected, + ['Rails::Command', :class, 'command_type'] => :private, + ['Rails::Command', :class, 'lookup_paths'] => :private, + ['Rails::Command', :class, 'file_lookup_paths'] => :private, + ['Rails::Railtie', :instance, 'run_console_blocks'] => :protected, + ['Rails::Railtie', :instance, 'run_generators_blocks'] => :protected, + ['Rails::Railtie', :instance, 'run_runner_blocks'] => :protected, + ['Rails::Railtie', :instance, 'run_tasks_blocks'] => :protected, + ['ActionController::Base', :instance, '_protected_ivars'] => :private, + ['ActionView::Template', :instance, 'method_name'] => :public, + ['Module', :instance, 'ruby2_keywords'] => :private, + ['Nokogiri::XML::Node', :instance, 'coerce'] => :protected, + ['Nokogiri::XML::Document', :class, 'empty_doc?'] => :private, + ['Nokogiri::Decorators::Slop', :instance, 'respond_to_missing?'] => :public, + ['RuboCop::Cop::RangeHelp', :instance, 'source_range'] => :private, + ['AST::Node', :instance, 'original_dup'] => :private, + ['Rainbow::Presenter', :instance, 'wrap_with_sgr'] => :private + }.freeze + private_constant :VISIBILITY_OVERRIDE + + # @param decl [RBS::AST::Members::MethodDefinition, RBS::AST::Members::AttrReader, + # RBS::AST::Members::AttrWriter, RBS::AST::Members::AttrAccessor] # @param closure [Pin::Closure] # @param context [Context] # @param scope [Symbol] :instance or :class # @param name [String] The name of the method # @return [Symbol] - def calculate_method_visibility(decl, context, closure, scope, name) + def calculate_method_visibility decl, context, closure, scope, name override_key = [closure.path, scope, name] visibility = VISIBILITY_OVERRIDE[override_key] simple_override_key = [closure.path, scope] visibility ||= VISIBILITY_OVERRIDE[simple_override_key] - visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(decl.name) + if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(decl.name) + visibility ||= :private + end if decl.kind == :singleton_instance # this is a 'module function' visibility ||= :private @@ -393,7 +501,9 @@ def method_def_to_pin decl, closure, context # having different type params / orders - we may need to match # this data model and have generics live in signatures to # handle those correctly - generics = decl.overloads.map(&:method_type).flat_map(&:type_params).map(&:name).map(&:to_s).uniq + generics = decl.overloads.map(&:method_type).map do |method_type| + type_parameter_names method_type + end if decl.instance? name = decl.name.to_s @@ -417,24 +527,23 @@ def method_def_to_pin decl, closure, context pin.instance_variable_set(:@return_type, ComplexType::VOID) end end - if decl.singleton? - final_scope = :class - name = decl.name.to_s - visibility = calculate_method_visibility(decl, context, closure, final_scope, name) - pin = Solargraph::Pin::Method.new( - name: name, - closure: closure, - comments: decl.comment&.string, - type_location: location_decl_to_pin_location(decl.location), - visibility: visibility, - scope: final_scope, - signatures: [], - generics: generics, - source: :rbs - ) - pin.signatures.concat method_def_to_sigs(decl, pin) - pins.push pin - end + return unless decl.singleton? + final_scope = :class + name = decl.name.to_s + visibility = calculate_method_visibility(decl, context, closure, final_scope, name) + pin = Solargraph::Pin::Method.new( + name: name, + closure: closure, + comments: decl.comment&.string, + type_location: location_decl_to_pin_location(decl.location), + visibility: visibility, + scope: final_scope, + signatures: [], + generics: generics, + source: :rbs + ) + pin.signatures.concat method_def_to_sigs(decl, pin) + pins.push pin end # @param decl [RBS::AST::Members::MethodDefinition] @@ -444,22 +553,24 @@ def method_def_to_sigs decl, pin # @param overload [RBS::AST::Members::MethodDefinition::Overload] decl.overloads.map do |overload| type_location = location_decl_to_pin_location(overload.method_type.location) - generics = overload.method_type.type_params.map(&:name).map(&:to_s) + generics = type_parameter_names(overload.method_type) signature_parameters, signature_return_type = parts_of_function(overload.method_type, pin) rbs_block = overload.method_type.block block = if rbs_block block_parameters, block_return_type = parts_of_function(rbs_block, pin) - Pin::Signature.new(generics: generics, parameters: block_parameters, return_type: block_return_type, source: :rbs, + Pin::Signature.new(generics: generics, parameters: block_parameters, + return_type: block_return_type, source: :rbs, type_location: type_location, closure: pin) end - Pin::Signature.new(generics: generics, parameters: signature_parameters, return_type: signature_return_type, block: block, source: :rbs, + Pin::Signature.new(generics: generics, parameters: signature_parameters, + return_type: signature_return_type, block: block, source: :rbs, type_location: type_location, closure: pin) end end # @param location [RBS::Location, nil] # @return [Solargraph::Location, nil] - def location_decl_to_pin_location(location) + def location_decl_to_pin_location location return nil if location&.name.nil? # @sg-ignore flow sensitive typing should handle return nil if location&.name.nil? @@ -471,15 +582,16 @@ def location_decl_to_pin_location(location) Location.new(location.name.to_s, range) end - # @param type [RBS::MethodType,RBS::Types::Block] + # @param type [RBS::MethodType, RBS::Types::Block] # @param pin [Pin::Method] # @return [Array(Array, ComplexType)] def parts_of_function type, pin type_location = pin.type_location if defined?(RBS::Types::UntypedFunction) && type.type.is_a?(RBS::Types::UntypedFunction) return [ - [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, type_location: type_location)], - ComplexType.try_parse(method_type_to_tag(type)).force_rooted + [Solargraph::Pin::Parameter.new(decl: :restarg, name: 'arg', closure: pin, source: :rbs, + type_location: type_location)], + method_type_to_type(type) ] end @@ -488,41 +600,43 @@ def parts_of_function type, pin type.type.required_positionals.each do |param| # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - # @sg-ignore RBS generic type understanding issue - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, source: :rbs, type_location: type_location) + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, + # @sg-ignore RBS generic type understanding issue + return_type: other_type_to_type(param.type), + source: :rbs, type_location: type_location) end type.type.optional_positionals.each do |param| # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :optarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: other_type_to_type(param.type), type_location: type_location, source: :rbs) end if type.type.rest_positionals name = type.type.rest_positionals.name ? type.type.rest_positionals.name.to_s : "arg_#{arg_num += 1}" - inner_rest_positional_type = - ComplexType.try_parse(other_type_to_tag(type.type.rest_positionals.type)) + inner_rest_positional_type = other_type_to_type(type.type.rest_positionals.type) rest_positional_type = ComplexType::UniqueType.new('Array', [], [inner_rest_positional_type], rooted: true, parameters_type: :list) parameters.push Solargraph::Pin::Parameter.new(decl: :restarg, name: name, closure: pin, source: :rbs, type_location: type_location, - return_type: rest_positional_type,) + return_type: rest_positional_type) end type.type.trailing_positionals.each do |param| # @sg-ignore RBS generic type understanding issue name = param.name ? param.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, type_location: type_location) + parameters.push Solargraph::Pin::Parameter.new(decl: :arg, name: name, closure: pin, source: :rbs, + type_location: type_location) end type.type.required_keywords.each do |orig, param| # @sg-ignore RBS generic type understanding issue name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: other_type_to_type(param.type), source: :rbs, type_location: type_location) end type.type.optional_keywords.each do |orig, param| @@ -530,18 +644,18 @@ def parts_of_function type, pin name = orig ? orig.to_s : "arg_#{arg_num += 1}" parameters.push Solargraph::Pin::Parameter.new(decl: :kwoptarg, name: name, closure: pin, # @sg-ignore RBS generic type understanding issue - return_type: ComplexType.try_parse(other_type_to_tag(param.type)).force_rooted, + return_type: other_type_to_type(param.type), type_location: type_location, source: :rbs) end if type.type.rest_keywords name = type.type.rest_keywords.name ? type.type.rest_keywords.name.to_s : "arg_#{arg_num += 1}" - parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, name: type.type.rest_keywords.name.to_s, closure: pin, + parameters.push Solargraph::Pin::Parameter.new(decl: :kwrestarg, + name: type.type.rest_keywords.name.to_s, closure: pin, source: :rbs, type_location: type_location) end - rooted_tag = method_type_to_tag(type) - return_type = ComplexType.try_parse(rooted_tag).force_rooted + return_type = method_type_to_type(type) [parameters, return_type] end @@ -549,7 +663,7 @@ def parts_of_function type, pin # @param closure [Pin::Namespace] # @param context [Context] # @return [void] - def attr_reader_to_pin(decl, closure, context) + def attr_reader_to_pin decl, closure, context name = decl.name.to_s final_scope = decl.kind == :instance ? :instance : :class visibility = calculate_method_visibility(decl, context, closure, final_scope, name) @@ -563,9 +677,11 @@ def attr_reader_to_pin(decl, closure, context) visibility: visibility, source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) - logger.debug { "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" } + logger.debug do + "Conversions#attr_reader_to_pin(name=#{name.inspect}, visibility=#{visibility.inspect}) => #{pin.inspect}" + end pins.push pin end @@ -573,9 +689,9 @@ def attr_reader_to_pin(decl, closure, context) # @param closure [Pin::Namespace] # @param context [Context] # @return [void] - def attr_writer_to_pin(decl, closure, context) + def attr_writer_to_pin decl, closure, context final_scope = decl.kind == :instance ? :instance : :class - name = "#{decl.name.to_s}=" + name = "#{decl.name}=" visibility = calculate_method_visibility(decl, context, closure, final_scope, name) type_location = location_decl_to_pin_location(decl.location) pin = Solargraph::Pin::Method.new( @@ -592,13 +708,13 @@ def attr_writer_to_pin(decl, closure, context) pin.parameters << Solargraph::Pin::Parameter.new( name: 'value', - return_type: ComplexType.try_parse(other_type_to_tag(decl.type)).force_rooted, + return_type: other_type_to_type(decl.type), source: :rbs, closure: pin, type_location: type_location ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags - pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tag)) + rooted_tags = other_type_to_type(decl.type).rooted_tags + pin.docstring.add_tag(YARD::Tags::Tag.new(:return, '', rooted_tags)) pins.push pin end @@ -606,7 +722,7 @@ def attr_writer_to_pin(decl, closure, context) # @param closure [Pin::Namespace] # @param context [Context] # @return [void] - def attr_accessor_to_pin(decl, closure, context) + def attr_accessor_to_pin decl, closure, context attr_reader_to_pin(decl, closure, context) attr_writer_to_pin(decl, closure, context) end @@ -614,7 +730,7 @@ def attr_accessor_to_pin(decl, closure, context) # @param decl [RBS::AST::Members::InstanceVariable] # @param closure [Pin::Namespace] # @return [void] - def ivar_to_pin(decl, closure) + def ivar_to_pin decl, closure pin = Solargraph::Pin::InstanceVariable.new( name: decl.name.to_s, closure: closure, @@ -622,7 +738,7 @@ def ivar_to_pin(decl, closure) comments: decl.comment&.string, source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -630,7 +746,7 @@ def ivar_to_pin(decl, closure) # @param decl [RBS::AST::Members::ClassVariable] # @param closure [Pin::Namespace] # @return [void] - def cvar_to_pin(decl, closure) + def cvar_to_pin decl, closure name = decl.name.to_s pin = Solargraph::Pin::ClassVariable.new( name: name, @@ -639,7 +755,7 @@ def cvar_to_pin(decl, closure) type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -647,7 +763,7 @@ def cvar_to_pin(decl, closure) # @param decl [RBS::AST::Members::ClassInstanceVariable] # @param closure [Pin::Namespace] # @return [void] - def civar_to_pin(decl, closure) + def civar_to_pin decl, closure name = decl.name.to_s pin = Solargraph::Pin::InstanceVariable.new( name: name, @@ -656,7 +772,7 @@ def civar_to_pin(decl, closure) type_location: location_decl_to_pin_location(decl.location), source: :rbs ) - rooted_tag = ComplexType.parse(other_type_to_tag(decl.type)).force_rooted.rooted_tags + rooted_tag = other_type_to_type(decl.type).rooted_tags pin.docstring.add_tag(YARD::Tags::Tag.new(:type, '', rooted_tag)) pins.push pin end @@ -666,9 +782,9 @@ def civar_to_pin(decl, closure) # @return [void] def include_to_pin decl, closure type = build_type(decl.name, decl.args) - generic_values = type.all_params.map(&:to_s) + generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Include.new( - name: decl.name.relative!.to_s, + name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(decl.location), generic_values: generic_values, closure: closure, @@ -680,9 +796,12 @@ def include_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def prepend_to_pin decl, closure + type = build_type(decl.name, decl.args) + generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Prepend.new( - name: decl.name.relative!.to_s, + name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(decl.location), + generic_values: generic_values, closure: closure, source: :rbs ) @@ -692,9 +811,12 @@ def prepend_to_pin decl, closure # @param closure [Pin::Namespace] # @return [void] def extend_to_pin decl, closure + type = build_type(decl.name, decl.args) + generic_values = type.all_params.map(&:rooted_tags) pins.push Solargraph::Pin::Reference::Extend.new( - name: decl.name.relative!.to_s, + name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(decl.location), + generic_values: generic_values, closure: closure, source: :rbs ) @@ -711,114 +833,98 @@ def alias_to_pin decl, closure original: decl.old_name.to_s, closure: closure, scope: final_scope, - source: :rbs, + source: :rbs ) end - RBS_TO_YARD_TYPE = { - 'bool' => 'Boolean', - 'string' => 'String', - 'int' => 'Integer', - 'untyped' => '', - 'NilClass' => 'nil' - } - # @param type [RBS::MethodType, RBS::Types::Block] - # @return [String] - def method_type_to_tag type + # @return [ComplexType, ComplexType::UniqueType] + def method_type_to_type type if type_aliases.key?(type.type.return_type.to_s) - other_type_to_tag(type_aliases[type.type.return_type.to_s].type) - else - other_type_to_tag type.type.return_type - end - end - - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [ComplexType::UniqueType] - def build_type(type_name, type_args = []) - base = RBS_TO_YARD_TYPE[type_name.relative!.to_s] || type_name.relative!.to_s - params = type_args.map { |a| other_type_to_tag(a) }.map do |t| - ComplexType.try_parse(t).force_rooted - end - if base == 'Hash' && params.length == 2 - ComplexType::UniqueType.new(base, [params.first], [params.last], rooted: true, parameters_type: :hash) + other_type_to_type(type_aliases[type.type.return_type.to_s].type) else - ComplexType::UniqueType.new(base, [], params.reject(&:undefined?), rooted: true, parameters_type: :list) + other_type_to_type type.type.return_type end end - # @param type_name [RBS::TypeName] - # @param type_args [Enumerable] - # @return [String] - def type_tag(type_name, type_args = []) - build_type(type_name, type_args).tags - end - # @param type [RBS::Types::Bases::Base,Object] RBS type object. # Note: Generally these extend from RBS::Types::Bases::Base, # but not all. - # @return [String] - def other_type_to_tag type - if type.is_a?(RBS::Types::Optional) - "#{other_type_to_tag(type.type)}, nil" - elsif type.is_a?(RBS::Types::Bases::Any) - 'undefined' - elsif type.is_a?(RBS::Types::Bases::Bool) - 'Boolean' - elsif type.is_a?(RBS::Types::Tuple) - "Array(#{type.types.map { |t| other_type_to_tag(t) }.join(', ')})" - elsif type.is_a?(RBS::Types::Literal) - type.literal.inspect - elsif type.is_a?(RBS::Types::Union) - type.types.map { |t| other_type_to_tag(t) }.join(', ') - elsif type.is_a?(RBS::Types::Record) + # + # @return [ComplexType, ComplexType::UniqueType] + def other_type_to_type type + case type + when RBS::Types::Optional + # @sg-ignore flow based typing needs to understand case when class pattern + ComplexType.new([other_type_to_type(type.type), + ComplexType::UniqueType::NIL]) + when RBS::Types::Bases::Any + ComplexType::UNDEFINED + when RBS::Types::Bases::Bool + ComplexType::BOOLEAN + when RBS::Types::Tuple + # @sg-ignore flow based typing needs to understand case when class pattern + tuple_types = type.types.map { |t| other_type_to_type(t) } + ComplexType::UniqueType.new('Array', [], tuple_types, rooted: true, parameters_type: :fixed) + when RBS::Types::Literal + # @sg-ignore flow based typing needs to understand case when class pattern + ComplexType.try_parse(type.literal.inspect).force_rooted + when RBS::Types::Union + # @sg-ignore flow based typing needs to understand case when class pattern + ComplexType.new(type.types.map { |t| other_type_to_type(t) }) + when RBS::Types::Record # @todo Better record support - 'Hash' - elsif type.is_a?(RBS::Types::Bases::Nil) - 'nil' - elsif type.is_a?(RBS::Types::Bases::Self) - 'self' - elsif type.is_a?(RBS::Types::Bases::Void) - 'void' - elsif type.is_a?(RBS::Types::Variable) - "#{Solargraph::ComplexType::GENERIC_TAG_NAME}<#{type.name}>" - elsif type.is_a?(RBS::Types::ClassInstance) #&& !type.args.empty? - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Bases::Instance) - 'self' - elsif type.is_a?(RBS::Types::Bases::Top) + ComplexType::UniqueType.new('Hash', rooted: true) + when RBS::Types::Bases::Nil + ComplexType::NIL + when RBS::Types::Bases::Self + ComplexType::SELF + when RBS::Types::Bases::Void + ComplexType::VOID + when RBS::Types::Variable + # @sg-ignore flow based typing needs to understand case when class pattern + ComplexType.parse("generic<#{type.name}>").force_rooted + when RBS::Types::ClassInstance # && !type.args.empty? + # @sg-ignore flow based typing needs to understand case when class pattern + build_type(type.name, type.args) + when RBS::Types::Bases::Instance + ComplexType::SELF + when RBS::Types::Bases::Top # top is the most super superclass - 'BasicObject' - elsif type.is_a?(RBS::Types::Bases::Bottom) + ComplexType::UniqueType.new('BasicObject', rooted: true) + when RBS::Types::Bases::Bottom # bottom is used in contexts where nothing will ever return # - e.g., it could be the return type of 'exit()' or 'raise' # # @todo define a specific bottom type and use it to # determine dead code - 'undefined' - elsif type.is_a?(RBS::Types::Intersection) - type.types.map { |member| other_type_to_tag(member) }.join(', ') - elsif type.is_a?(RBS::Types::Proc) - 'Proc' - elsif type.is_a?(RBS::Types::Alias) + ComplexType::UNDEFINED + when RBS::Types::Intersection + # @sg-ignore flow based typing needs to understand case when class pattern + ComplexType.new(type.types.map { |member| other_type_to_type(member) }) + when RBS::Types::Proc + ComplexType::UniqueType.new('Proc', rooted: true) + when RBS::Types::Alias # type-level alias use - e.g., 'bool' in "type bool = true | false" # @todo ensure these get resolved after processing all aliases # @todo handle recursive aliases - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::Interface) + # @sg-ignore flow based typing needs to understand case when class pattern + build_type(type.name, type.args) + when RBS::Types::Interface # represents a mix-in module which can be considered a # subtype of a consumer of it - type_tag(type.name, type.args) - elsif type.is_a?(RBS::Types::ClassSingleton) + # @sg-ignore flow based typing needs to understand case when class pattern + build_type(type.name, type.args) + when RBS::Types::ClassSingleton # e.g., singleton(String) - type_tag(type.name) + # @sg-ignore flow based typing needs to understand case when class pattern + build_type(type.name) else # RBS doesn't provide a common base class for its type AST nodes # # @sg-ignore all types should include location Solargraph.logger.warn "Unrecognized RBS type: #{type.class} at #{type.location}" - 'undefined' + ComplexType::UNDEFINED end end @@ -831,9 +937,9 @@ def add_mixins decl, namespace # @todo are we handling prepend correctly? klass = mixin.is_a?(RBS::AST::Members::Include) ? Pin::Reference::Include : Pin::Reference::Extend type = build_type(mixin.name, mixin.args) - generic_values = type.all_params.map(&:to_s) + generic_values = type.all_params.map(&:rooted_tags) pins.push klass.new( - name: mixin.name.relative!.to_s, + name: type.rooted_name, # reference pins use rooted names type_location: location_decl_to_pin_location(mixin.location), generic_values: generic_values, closure: namespace, diff --git a/lib/solargraph/rbs_map/core_fills.rb b/lib/solargraph/rbs_map/core_fills.rb index 3bb32a0da..0116b15eb 100644 --- a/lib/solargraph/rbs_map/core_fills.rb +++ b/lib/solargraph/rbs_map/core_fills.rb @@ -19,7 +19,7 @@ module CoreFills Solargraph::Pin::Method.new(name: 'class', scope: :instance, closure: Solargraph::Pin::Namespace.new(name: 'Object', source: :core_fill), comments: '@return [::Class]', source: :core_fill) - ] + ].freeze OVERRIDES = [ Override.from_comment('BasicObject#instance_eval', '@yieldreceiver [self]', @@ -38,43 +38,50 @@ module CoreFills source: :core_fill), # RBS does not define Class with a generic, so all calls to # generic() return an 'untyped'. We can do better: - Override.method_return('Class#allocate', 'self', source: :core_fill), - ] + Override.method_return('Class#allocate', 'self', source: :core_fill) + ].freeze # @todo I don't see any direct link in RBS to build this from - # presumably RBS is using duck typing to match interfaces # against concrete classes INCLUDES = [ Solargraph::Pin::Reference::Include.new(name: '_ToAry', - closure: Solargraph::Pin::Namespace.new(name: 'Array', source: :core_fill), + closure: Solargraph::Pin::Namespace.new(name: 'Array', + source: :core_fill), generic_values: ['generic'], source: :core_fill), Solargraph::Pin::Reference::Include.new(name: '_ToAry', - closure: Solargraph::Pin::Namespace.new(name: 'Set', source: :core_fill), + closure: Solargraph::Pin::Namespace.new(name: 'Set', + source: :core_fill), generic_values: ['generic'], source: :core_fill), Solargraph::Pin::Reference::Include.new(name: '_Each', - closure: Solargraph::Pin::Namespace.new(name: 'Array', source: :core_fill), + closure: Solargraph::Pin::Namespace.new(name: 'Array', + source: :core_fill), generic_values: ['generic'], source: :core_fill), Solargraph::Pin::Reference::Include.new(name: '_Each', - closure: Solargraph::Pin::Namespace.new(name: 'Set', source: :core_fill), + closure: Solargraph::Pin::Namespace.new(name: 'Set', + source: :core_fill), generic_values: ['generic'], source: :core_fill), Solargraph::Pin::Reference::Include.new(name: '_ToS', - closure: Solargraph::Pin::Namespace.new(name: 'Object', source: :core_fill), + closure: Solargraph::Pin::Namespace.new(name: 'Object', + source: :core_fill), source: :core_fill), Solargraph::Pin::Reference::Include.new(name: '_ToS', - closure: Solargraph::Pin::Namespace.new(name: 'String', source: :core_fill), + closure: Solargraph::Pin::Namespace.new(name: 'String', + source: :core_fill), source: :core_fill) - ] + ].freeze # HACK: Add Errno exception classes errno = Solargraph::Pin::Namespace.new(name: 'Errno', source: :core_fill) errnos = [] Errno.constants.each do |const| errnos.push Solargraph::Pin::Namespace.new(type: :class, name: const.to_s, closure: errno, source: :core_fill) - errnos.push Solargraph::Pin::Reference::Superclass.new(closure: errnos.last, name: 'SystemCallError', source: :core_fill) + errnos.push Solargraph::Pin::Reference::Superclass.new(closure: errnos.last, name: 'SystemCallError', + source: :core_fill) end ERRNOS = errnos diff --git a/lib/solargraph/rbs_map/stdlib_map.rb b/lib/solargraph/rbs_map/stdlib_map.rb index a92410895..e6ebcf90f 100644 --- a/lib/solargraph/rbs_map/stdlib_map.rb +++ b/lib/solargraph/rbs_map/stdlib_map.rb @@ -12,7 +12,6 @@ class StdlibMap < RbsMap # @type [Hash{String => RbsMap}] @stdlib_maps_hash = {} - # @param rebuild [Boolean] build pins regardless of whether we # have cached them already # @param library [String] diff --git a/lib/solargraph/server_methods.rb b/lib/solargraph/server_methods.rb index bdac3f19c..86cd107d0 100644 --- a/lib/solargraph/server_methods.rb +++ b/lib/solargraph/server_methods.rb @@ -7,7 +7,7 @@ module ServerMethods # @return [Integer] def available_port socket = Socket.new(:INET, :STREAM, 0) - socket.bind(Addrinfo.tcp("127.0.0.1", 0)) + socket.bind(Addrinfo.tcp('127.0.0.1', 0)) port = socket.local_address.ip_port socket.close port diff --git a/lib/solargraph/shell.rb b/lib/solargraph/shell.rb index e2c626528..8ee13eacf 100755 --- a/lib/solargraph/shell.rb +++ b/lib/solargraph/shell.rb @@ -16,7 +16,7 @@ def self.exit_on_failure? map %w[--version -v] => :version - desc "--version, -v", "Print the version" + desc '--version, -v', 'Print the version' # @return [void] def version puts Solargraph::VERSION @@ -31,15 +31,15 @@ def socket port = options[:port] port = available_port if port.zero? Backport.run do - Signal.trap("INT") do + Signal.trap('INT') do Backport.stop end - Signal.trap("TERM") do + Signal.trap('TERM') do Backport.stop end # @sg-ignore Wrong argument type for Backport.prepare_tcp_server: adapter expected Backport::Adapter, received Module Backport.prepare_tcp_server host: options[:host], port: port, adapter: Solargraph::LanguageServer::Transport::Adapter - STDERR.puts "Solargraph is listening PORT=#{port} PID=#{Process.pid}" + warn "Solargraph is listening PORT=#{port} PID=#{Process.pid}" end end @@ -48,15 +48,15 @@ def socket def stdio require 'backport' Backport.run do - Signal.trap("INT") do + Signal.trap('INT') do Backport.stop end - Signal.trap("TERM") do + Signal.trap('TERM') do Backport.stop end # @sg-ignore Wrong argument type for Backport.prepare_stdio_server: adapter expected Backport::Adapter, received Module Backport.prepare_stdio_server adapter: Solargraph::LanguageServer::Transport::Adapter - STDERR.puts "Solargraph is listening on stdio PID=#{Process.pid}" + warn "Solargraph is listening on stdio PID=#{Process.pid}" end end @@ -64,11 +64,11 @@ def stdio option :extensions, type: :boolean, aliases: :e, desc: 'Add installed extensions', default: true # @param directory [String] # @return [void] - def config(directory = '.') + def config directory = '.' matches = [] if options[:extensions] Gem::Specification.each do |g| - if g.name.match(/^solargraph\-[A-Za-z0-9_\-]*?\-ext/) + if g.name.match(/^solargraph-[A-Za-z0-9_-]*?-ext/) require g.name matches.push g.name end @@ -84,7 +84,7 @@ def config(directory = '.') File.open(File.join(directory, '.solargraph.yml'), 'w') do |file| file.puts conf.to_yaml end - STDOUT.puts "Configuration file initialized." + $stdout.puts 'Configuration file initialized.' end desc 'clear', 'Delete all cached documentation' @@ -93,7 +93,7 @@ def config(directory = '.') ) # @return [void] def clear - puts "Deleting all cached documentation (gems, core and stdlib)" + puts 'Deleting all cached documentation (gems, core and stdlib)' Solargraph::PinCache.clear end map 'clear-cache' => :clear @@ -109,7 +109,7 @@ def cache gem, version = nil # ' end - desc 'uncache GEM [...GEM]', "Delete specific cached gem documentation" + desc 'uncache GEM [...GEM]', 'Delete specific cached gem documentation' long_desc %( Specify one or more gem names to clear. 'core' or 'stdlib' may also be specified to clear cached system documentation. @@ -174,7 +174,7 @@ def gems *names if names.empty? workspace.cache_all_for_workspace!($stdout, rebuild: options[:rebuild]) else - $stderr.puts("Caching these gems: #{names}") + warn("Caching these gems: #{names}") names.each do |name| if name == 'core' PinCache.cache_core(out: $stdout) if !PinCache.core? || options[:rebuild] @@ -195,7 +195,7 @@ def gems *names # @sg-ignore Need to add nil check here warn e.backtrace.join("\n") end - $stderr.puts "Documentation cached for #{names.count} gems." + warn "Documentation cached for #{names.count} gems." end end @@ -212,7 +212,7 @@ def reporters Type checking levels are normal, typed, strict, and strong. ) - option :level, type: :string, aliases: [:mode, :m, :l], desc: 'Type checking level', default: 'normal' + option :level, type: :string, aliases: %i[mode m l], desc: 'Type checking level', default: 'normal' option :directory, type: :string, aliases: :d, desc: 'The workspace directory', default: '.' # @return [void] def typecheck *files @@ -231,21 +231,28 @@ def typecheck *files files.map! { |file| File.realpath(file) } end filecount = 0 - time = Benchmark.measure { + time = Benchmark.measure do files.each do |file| - checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym, workspace: workspace) + checker = TypeChecker.new(file, api_map: api_map, rules: rules, level: options[:level].to_sym, + workspace: workspace) problems = checker.problems next if problems.empty? problems.sort! { |a, b| a.location.range.start.line <=> b.location.range.start.line } - puts problems.map { |prob| "#{prob.location.filename}:#{prob.location.range.start.line + 1} - #{prob.message}" }.join("\n") + puts problems.map { |prob| + "#{prob.location.filename}:#{prob.location.range.start.line + 1} - #{prob.message}" + }.join("\n") filecount += 1 probcount += problems.length end - } + end puts "Typecheck finished in #{time.real} seconds." - puts "#{probcount} problem#{probcount != 1 ? 's' : ''} found#{files.length != 1 ? " in #{filecount} of #{files.length} files" : ''}." + puts "#{probcount} problem#{if probcount != 1 + 's' + end} found#{if files.length != 1 + " in #{filecount} of #{files.length} files" + end}." # " - exit 1 if probcount > 0 + exit 1 if probcount.positive? end desc 'scan', 'Test the workspace for problems' @@ -262,26 +269,26 @@ def scan directory = File.realpath(options[:directory]) # @type [Solargraph::ApiMap, nil] api_map = nil - time = Benchmark.measure { + time = Benchmark.measure do api_map = Solargraph::ApiMap.load_with_cache(directory, $stdout) # @sg-ignore flow sensitive typing should be able to handle redefinition api_map.pins.each do |pin| - begin - puts pin_description(pin) if options[:verbose] - pin.typify api_map - pin.probe api_map - rescue StandardError => e - # @todo to add nil check here - # @todo should warn on nil dereference below - STDERR.puts "Error testing #{pin_description(pin)} #{pin.location ? "at #{pin.location.filename}:#{pin.location.range.start.line + 1}" : ''}" - STDERR.puts "[#{e.class}]: #{e.message}" - # @todo Need to add nil check here - # @todo flow sensitive typing should be able to handle redefinition - STDERR.puts e.backtrace.join("\n") - exit 1 - end + puts pin_description(pin) if options[:verbose] + pin.typify api_map + pin.probe api_map + rescue StandardError => e + # @todo to add nil check here + # @todo should warn on nil dereference below + warn "Error testing #{pin_description(pin)} #{if pin.location + "at #{pin.location.filename}:#{pin.location.range.start.line + 1}" + end}" + warn "[#{e.class}]: #{e.message}" + # @todo Need to add nil check here + # @todo flow sensitive typing should be able to handle redefinition + warn e.backtrace.join("\n") + exit 1 end - } + end # @sg-ignore Need to add nil check here puts "Scanned #{directory} (#{api_map.pins.length} pins) in #{time.real} seconds." end @@ -296,12 +303,15 @@ def list puts "#{workspace.filenames.length} files total." end - desc 'pin [PATH]', 'Describe a pin', hide: true + desc 'pin [PATH]', 'Describe a pin' option :rbs, type: :boolean, desc: 'Output the pin as RBS', default: false - option :typify, type: :boolean, desc: 'Output the calculated return type of the pin from annotations', default: false + option :typify, type: :boolean, desc: 'Output the calculated return type of the pin from annotations', + default: false option :references, type: :boolean, desc: 'Show references', default: false - option :probe, type: :boolean, desc: 'Output the calculated return type of the pin from annotations and inference', default: false - option :stack, type: :boolean, desc: 'Show entire stack of a method pin by including definitions in superclasses', default: false + option :probe, type: :boolean, desc: 'Output the calculated return type of the pin from annotations and inference', + default: false + option :stack, type: :boolean, desc: 'Show entire stack of a method pin by including definitions in superclasses', + default: false # @param path [String] The path to the method pin, e.g. 'Class#method' or 'Class.method' # @return [void] def pin path @@ -326,7 +336,7 @@ def pin path pin = pins.first case pin when nil - $stderr.puts "Pin not found for path '#{path}'" + warn "Pin not found for path '#{path}'" exit 1 when Pin::Namespace if options[:references] @@ -354,21 +364,122 @@ def pin path end end + desc 'profile [FILE]', 'Profile go-to-definition performance using vernier' + option :directory, type: :string, aliases: :d, desc: 'The workspace directory', default: '.' + option :output_dir, type: :string, aliases: :o, desc: 'The output directory for profiles', default: './tmp/profiles' + option :line, type: :numeric, aliases: :l, desc: 'Line number (0-based)', default: 4 + option :column, type: :numeric, aliases: :c, desc: 'Column number', default: 10 + option :memory, type: :boolean, aliases: :m, desc: 'Include memory usage counter', default: true + # @param file [String, nil] + # @return [void] + def profile file = nil + begin + require 'vernier' + rescue LoadError + warn 'vernier gem not found. Install with: gem install vernier' + return + end + + hooks = [] + hooks << :memory_usage if options[:memory] + + directory = File.realpath(options[:directory]) + FileUtils.mkdir_p(options[:output_dir]) + + host = Solargraph::LanguageServer::Host.new + host.client_capabilities.merge!({ 'window' => { 'workDoneProgress' => true } }) + # @param method [String] The message method + # @param params [Hash] The method parameters + # @return [void] + def host.send_notification method, params + puts "Notification: #{method} - #{params}" + end + + puts 'Parsing and mapping source files...' + prepare_start = Time.now + Vernier.profile(out: "#{options[:output_dir]}/parse_benchmark.json.gz", hooks: hooks) do + puts 'Mapping libraries' + host.prepare(directory) + sleep 0.2 until host.libraries.all?(&:mapped?) + end + prepare_time = Time.now - prepare_start + + puts 'Building the catalog...' + catalog_start = Time.now + Vernier.profile(out: "#{options[:output_dir]}/catalog_benchmark.json.gz", hooks: hooks) do + host.catalog + end + catalog_time = Time.now - catalog_start + + # Determine test file + if file + test_file = File.join(directory, file) + else + test_file = File.join(directory, 'lib', 'other.rb') + unless File.exist?(test_file) + # Fallback to any Ruby file in the workspace + workspace = Solargraph::Workspace.new(directory) + test_file = workspace.filenames.find { |f| f.end_with?('.rb') } + unless test_file + warn 'No Ruby files found in workspace' + return + end + end + end + + file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path(test_file)) + + puts "Profiling go-to-definition for #{test_file}" + puts "Position: line #{options[:line]}, column #{options[:column]}" + + definition_start = Time.now + Vernier.profile(out: "#{options[:output_dir]}/definition_benchmark.json.gz", hooks: hooks) do + message = Solargraph::LanguageServer::Message::TextDocument::Definition.new( + host, { + 'params' => { + 'textDocument' => { 'uri' => file_uri }, + 'position' => { 'line' => options[:line], 'character' => options[:column] } + } + } + ) + puts 'Processing go-to-definition request...' + result = message.process + + puts "Result: #{result.inspect}" + end + definition_time = Time.now - definition_start + + puts "\n=== Timing Results ===" + puts "Parsing & mapping: #{(prepare_time * 1000).round(2)}ms" + puts "Catalog building: #{(catalog_time * 1000).round(2)}ms" + puts "Go-to-definition: #{(definition_time * 1000).round(2)}ms" + total_time = prepare_time + catalog_time + definition_time + puts "Total time: #{(total_time * 1000).round(2)}ms" + + puts "\nProfiles saved to:" + puts " - #{File.expand_path('parse_benchmark.json.gz', options[:output_dir])}" + puts " - #{File.expand_path('catalog_benchmark.json.gz', options[:output_dir])}" + puts " - #{File.expand_path('definition_benchmark.json.gz', options[:output_dir])}" + + puts "\nUpload the JSON files to https://vernier.prof/ to view the profiles." + puts 'Or use https://rubygems.org/gems/profile-viewer to view them locally.' + end + private # @param pin [Solargraph::Pin::Base] # @return [String] def pin_description pin desc = if pin.path.nil? || pin.path.empty? - if pin.closure - # @sg-ignore Need to add nil check here - "#{pin.closure.path} | #{pin.name}" - else - "#{pin.context.namespace} | #{pin.name}" - end - else - pin.path - end + if pin.closure + # @sg-ignore Need to add nil check here + "#{pin.closure.path} | #{pin.name}" + else + "#{pin.context.namespace} | #{pin.name}" + end + else + pin.path + end # @sg-ignore Need to add nil check here desc += " (#{pin.location.filename} #{pin.location.range.start.line})" if pin.location desc @@ -376,7 +487,7 @@ def pin_description pin # @param type [ComplexType, ComplexType::UniqueType] # @return [void] - def print_type(type) + def print_type type if options[:rbs] puts type.to_rbs else @@ -386,7 +497,7 @@ def print_type(type) # @param pin [Solargraph::Pin::Base] # @return [void] - def print_pin(pin) + def print_pin pin if options[:rbs] puts pin.to_rbs else diff --git a/lib/solargraph/source.rb b/lib/solargraph/source.rb index 615269d73..94147989e 100644 --- a/lib/solargraph/source.rb +++ b/lib/solargraph/source.rb @@ -66,7 +66,7 @@ def at range def from_to l1, c1, l2, c2 b = Solargraph::Position.line_char_to_offset(code, l1, c1) e = Solargraph::Position.line_char_to_offset(code, l2, c2) - code[b..e-1] + code[b..(e - 1)] end # Get the nearest node that contains the specified index. @@ -74,7 +74,7 @@ def from_to l1, c1, l2, c2 # @param line [Integer] # @param column [Integer] # @return [AST::Node] - def node_at(line, column) + def node_at line, column tree_at(line, column).first end @@ -84,7 +84,7 @@ def node_at(line, column) # @param line [Integer] # @param column [Integer] # @return [Array] - def tree_at(line, column) + def tree_at line, column position = Position.new(line, column) stack = [] inner_tree_at node, position, stack @@ -140,7 +140,7 @@ def string_at? position # @sg-ignore Need to add nil check here return true if node.type == :str && range.include?(position) && range.start != position # @sg-ignore Need to add nil check here - return true if [:STR, :str].include?(node.type) && range.include?(position) && range.start != position + return true if %i[STR str].include?(node.type) && range.include?(position) && range.start != position if node.type == :dstr inner = node_at(position.line, position.column) next if inner.nil? @@ -151,7 +151,7 @@ def string_at? position # @sg-ignore Need to add nil check here inner_code = at(Solargraph::Range.new(inner_range.start, position)) # @sg-ignore Need to add nil check here - return true if (inner.type == :dstr && inner_range.ending.character <= position.character) && !inner_code.end_with?('}') || + return true if (inner.type == :dstr && inner_range.ending.character <= position.character && !inner_code.end_with?('}')) || # @sg-ignore Need to add nil check here (inner.type != :dstr && inner_range.ending.line == position.line && position.character <= inner_range.ending.character && inner_code.end_with?('}')) end @@ -171,7 +171,7 @@ def string_ranges def comment_at? position comment_ranges.each do |range| return true if range.include?(position) || - (range.ending.line == position.line && range.ending.column < position.column) + (range.ending.line == position.line && range.ending.column < position.column) break if range.ending.line > position.line end false @@ -190,13 +190,13 @@ def error_ranges # @param node [Parser::AST::Node] # @return [String] - def code_for(node) + def code_for node rng = Range.from_node(node) # @sg-ignore Need to add nil check here b = Position.line_char_to_offset(code, rng.start.line, rng.start.column) # @sg-ignore Need to add nil check here e = Position.line_char_to_offset(code, rng.ending.line, rng.ending.column) - frag = code[b..e-1].to_s + frag = code[b..(e - 1)].to_s frag.strip.gsub(/,$/, '') end @@ -224,8 +224,8 @@ def location end FOLDING_NODE_TYPES = %i[ - class sclass module def defs if str dstr array while unless kwbegin hash block - ].freeze + class sclass module def defs if str dstr array while unless kwbegin hash block + ].freeze # Get an array of ranges that can be folded, e.g., the range of a class # definition or an if condition. @@ -293,9 +293,8 @@ def inner_folding_ranges top, result = [], parent = nil # @sg-ignore Translate to something flow sensitive typing understands range = Range.from_node(top) # @sg-ignore Need to add nil check here - if result.empty? || range.start.line > result.last.start.line - # @sg-ignore Need to add nil check here - result.push range unless range.ending.line - range.start.line < 2 + if (result.empty? || range.start.line > result.last.start.line) && range.ending.line - range.start.line >= 2 + result.push range end end # @sg-ignore Translate to something flow sensitive typing understands @@ -312,7 +311,7 @@ def stringify_comment_array comments ctxt = String.new('') started = false skip = nil - comments.lines.each { |l| + comments.lines.each do |l| # Trim the comment and minimum leading whitespace p = l.force_encoding('UTF-8').encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#+/, '') if p.strip.empty? @@ -322,10 +321,10 @@ def stringify_comment_array comments here = p.index(/[^ \t]/) # @sg-ignore flow sensitive typing should be able to handle redefinition skip = here if skip.nil? || here < skip - ctxt.concat p[skip..-1] + ctxt.concat p[skip..] end started = true - } + end ctxt end @@ -354,7 +353,7 @@ def foldable_comment_block_ranges return [] unless synchronized? result = [] grouped = [] - comments.keys.each do |l| + comments.each_key do |l| if grouped.empty? || l == grouped.last + 1 grouped.push l else @@ -372,11 +371,11 @@ def string_nodes_in n result = [] if Parser.is_ast_node?(n) # @sg-ignore Translate to something flow sensitive typing understands - if n.type == :str || n.type == :dstr || n.type == :STR || n.type == :DSTR + if %i[str dstr STR DSTR].include?(n.type) result.push n else # @sg-ignore Translate to something flow sensitive typing understands - n.children.each{ |c| result.concat string_nodes_in(c) } + n.children.each { |c| result.concat string_nodes_in(c) } end end result @@ -390,13 +389,12 @@ def inner_tree_at node, position, stack return if node.nil? here = Range.from_node(node) # @sg-ignore Need to add nil check here - if here.contain?(position) - stack.unshift node - node.children.each do |c| - next unless Parser.is_ast_node?(c) - next if c.loc.expression.nil? - inner_tree_at(c, position, stack) - end + return unless here.contain?(position) + stack.unshift node + node.children.each do |c| + next unless Parser.is_ast_node?(c) + next if c.loc.expression.nil? + inner_tree_at(c, position, stack) end end @@ -425,7 +423,7 @@ def finalize @node, @comments = Solargraph::Parser.parse_with_comments(@code, filename, 0) @parsed = true @repaired = @code - rescue Parser::SyntaxError, EncodingError => e + rescue Parser::SyntaxError, EncodingError @node = nil @comments = {} @parsed = false @@ -440,7 +438,7 @@ def finalize begin @node, @comments = Solargraph::Parser.parse_with_comments(@repaired, filename, 0) @parsed = true - rescue Parser::SyntaxError, EncodingError => e + rescue Parser::SyntaxError, EncodingError @node = nil @comments = {} @parsed = false @@ -453,7 +451,7 @@ def finalize # @param val [String] # @return [String] - def code=(val) + def code= val @code_lines = nil @finalized = false @code = val diff --git a/lib/solargraph/source/chain.rb b/lib/solargraph/source/chain.rb index a1e439ffb..4bd1b67b6 100644 --- a/lib/solargraph/source/chain.rb +++ b/lib/solargraph/source/chain.rb @@ -48,11 +48,6 @@ class Chain attr_reader :node - # @sg-ignore Fix "Not enough arguments to Module#protected" - protected def equality_fields - [links, node] - end - # @param node [Parser::AST::Node, nil] # @param links [::Array] # @param splat [Boolean] @@ -119,7 +114,9 @@ def define api_map, name_pin, locals pins = link.resolve(api_map, working_pin, locals) type = infer_from_definitions(pins, working_pin, api_map, locals) if type.undefined? - logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) => [] - undefined type from #{link.desc}" } + logger.debug do + "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) => [] - undefined type from #{link.desc}" + end return [] end # We continue to use the context from the head pin, in case @@ -128,7 +125,9 @@ def define api_map, name_pin, locals # for the binder, as this is chaining off of it, and the # binder is now the lhs of the rhs we are evaluating. working_pin = Pin::ProxyType.anonymous(name_pin.context, binder: type, closure: name_pin, source: :chain) - logger.debug { "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) - after processing #{link.desc}, new working_pin=#{working_pin} with binder #{working_pin.binder}" } + logger.debug do + "Chain#define(links=#{links.map(&:desc)}, name_pin=#{name_pin.inspect}, locals=#{locals}) - after processing #{link.desc}, new working_pin=#{working_pin} with binder #{working_pin.binder}" + end end links.last.last_context = working_pin links.last.resolve(api_map, working_pin, locals) @@ -150,7 +149,9 @@ def infer api_map, name_pin, locals @@inference_cache = {} end out = infer_uncached(api_map, name_pin, locals).downcast_to_literal_if_possible - logger.debug { "Chain#infer() - caching result - cache_key_hash=#{cache_key.hash}, links.map(&:hash)=#{links.map(&:hash)}, links=#{links}, cache_key.map(&:hash) = #{cache_key.map(&:hash)}, cache_key=#{cache_key}" } + logger.debug do + "Chain#infer() - caching result - cache_key_hash=#{cache_key.hash}, links.map(&:hash)=#{links.map(&:hash)}, links=#{links}, cache_key.map(&:hash) = #{cache_key.map(&:hash)}, cache_key=#{cache_key}" + end @@inference_cache[cache_key] = out end @@ -161,12 +162,16 @@ def infer api_map, name_pin, locals def infer_uncached api_map, name_pin, locals pins = define(api_map, name_pin, locals) if pins.empty? - logger.debug { "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}) => undefined - no pins" } + logger.debug do + "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}) => undefined - no pins" + end return ComplexType::UNDEFINED end type = infer_from_definitions(pins, links.last.last_context, api_map, locals) out = maybe_nil(type) - logger.debug { "Chain#infer_uncached(links=#{self.links.map(&:desc)}, locals=#{locals.map(&:desc)}, name_pin=#{name_pin}, name_pin.closure=#{name_pin.closure.inspect}, name_pin.binder=#{name_pin.binder}) => #{out.rooted_tags.inspect}" } + logger.debug do + "Chain#infer_uncached(links=#{links.map(&:desc)}, locals=#{locals.map(&:desc)}, name_pin=#{name_pin}, name_pin.closure=#{name_pin.closure.inspect}, name_pin.binder=#{name_pin.binder}) => #{out.rooted_tags.inspect}" + end out end @@ -245,17 +250,13 @@ def infer_from_definitions pins, name_pin, api_map, locals end # Limit method inference recursion - if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method) - return ComplexType::UNDEFINED - end + return ComplexType::UNDEFINED if @@inference_depth >= 10 && pins.first.is_a?(Pin::Method) @@inference_depth += 1 # @param pin [Pin::Base] unresolved_pins.each do |pin| # Avoid infinite recursion - if @@inference_stack.include?(pin.identity) - next - end + next if @@inference_stack.include?(pin.identity) @@inference_stack.push(pin.identity) type = pin.probe(api_map) @@ -289,6 +290,13 @@ def maybe_nil type return type unless nullable? ComplexType.new(type.items + [ComplexType::NIL]) end + + protected + + # @sg-ignore Fix "Not enough arguments to Module#protected" + def equality_fields + [links, node] + end end end end diff --git a/lib/solargraph/source/chain/array.rb b/lib/solargraph/source/chain/array.rb index 544934d8a..352ccf8f0 100644 --- a/lib/solargraph/source/chain/array.rb +++ b/lib/solargraph/source/chain/array.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph class Source class Chain @@ -20,11 +22,11 @@ def resolve api_map, name_pin, locals child_types = @children.map do |child| child.infer(api_map, name_pin, locals).simplify_literals end - type = if child_types.length == 0 || child_types.any?(&:undefined?) + type = if child_types.empty? || child_types.any?(&:undefined?) ComplexType::UniqueType.new('Array', rooted: true) elsif child_types.uniq.length == 1 && child_types.first.defined? ComplexType::UniqueType.new('Array', [], child_types.uniq, rooted: true, parameters_type: :list) - elsif child_types.length == 0 + elsif child_types.empty? ComplexType::UniqueType.new('Array', rooted: true, parameters_type: :list) else ComplexType::UniqueType.new('Array', [], child_types, rooted: true, parameters_type: :fixed) diff --git a/lib/solargraph/source/chain/call.rb b/lib/solargraph/source/chain/call.rb index eb83ead98..7d71077c1 100644 --- a/lib/solargraph/source/chain/call.rb +++ b/lib/solargraph/source/chain/call.rb @@ -28,19 +28,14 @@ class Call < Chain::Link # @param arguments [::Array] # @param block [Chain, nil] def initialize word, location = nil, arguments = [], block = nil - @word = word + super(word) + @location = location @arguments = arguments @block = block fix_block_pass end - # @sg-ignore Fix "Not enough arguments to Module#protected" - protected def equality_fields - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array - super + [arguments, block] - end - def with_block? !!@block end @@ -68,10 +63,7 @@ def resolve api_map, name_pin, locals stack = api_map.get_method_stack(ns_tag, word, scope: context.scope) [stack.first].compact end - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array - if !api_map.loose_unions && pin_groups.any? { |pins| pins.empty? } - pin_groups = [] - end + pin_groups = [] if !api_map.loose_unions && pin_groups.any?(&:empty?) pins = pin_groups.flatten.uniq(&:path) return [] if pins.empty? inferred_pins(pins, api_map, name_pin, locals) @@ -128,38 +120,42 @@ def inferred_pins pins, api_map, name_pin, locals if ol.block && with_block? block_atypes = ol.block.parameters.map(&:return_type) # @todo Need to add nil check here - if block.links.map(&:class) == [BlockSymbol] - # like the bar in foo(&:bar) - blocktype = block_symbol_call_type(api_map, name_pin.context, block_atypes, locals) - else - blocktype = block_call_type(api_map, name_pin, locals) - end + blocktype = if block.links.map(&:class) == [BlockSymbol] + # like the bar in foo(&:bar) + block_symbol_call_type(api_map, name_pin.context, block_atypes, locals) + else + block_call_type(api_map, name_pin, locals) + end end # @type new_signature_pin [Pin::Signature] - new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil, blocktype) + new_signature_pin = ol.resolve_generics_from_context_until_complete(ol.generics, atypes, nil, nil, + blocktype) new_return_type = new_signature_pin.return_type - if head? - # If we're at the head of the chain, we called a - # method somewhere that marked itself as returning - # self. Given we didn't invoke this on an object, - # this must be a method in this same class - so we - # use our own self type - self_type = name_pin.context - else - # if we're past the head in the chain, whatever the - # type of the lhs side is what 'self' will be in its - # declaration - we can't just use the type of the - # method pin, as this might be a subclass of the - # place where the method is defined - self_type = name_pin.binder - end + self_type = if head? + # If we're at the head of the chain, we called a + # method somewhere that marked itself as returning + # self. Given we didn't invoke this on an object, + # this must be a method in this same class - so we + # use our own self type + name_pin.context + else + # if we're past the head in the chain, whatever the + # type of the lhs side is what 'self' will be in its + # declaration - we can't just use the type of the + # method pin, as this might be a subclass of the + # place where the method is defined + name_pin.binder + end # This same logic applies to the YARD work done by # 'with_params()'. # # qualify(), however, happens in the namespace where # the docs were written - from the method pin. # @todo Need to add nil check here - type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, *p.gates) if new_return_type.defined? + if new_return_type.defined? + type = with_params(new_return_type.self_to_type(self_type), self_type).qualify(api_map, + *p.gates) + end type ||= ComplexType::UNDEFINED end break if type.defined? @@ -177,8 +173,10 @@ def inferred_pins pins, api_map, name_pin, locals end p end - logger.debug { "Call#inferred_pins(name_pin.binder=#{name_pin.binder}, word=#{word}, pins=#{pins.map(&:desc)}, name_pin=#{name_pin}) - result=#{result}" } - out = result.map do |pin| + logger.debug do + "Call#inferred_pins(name_pin.binder=#{name_pin.binder}, word=#{word}, pins=#{pins.map(&:desc)}, name_pin=#{name_pin}) - result=#{result}" + end + result.map do |pin| if pin.path == 'Class#new' && name_pin.binder.tag != 'Class' reduced_context = name_pin.binder.reduce_class_type pin.proxy(reduced_context) @@ -234,7 +232,7 @@ def process_directive pin, api_map, context, locals # @param locals [::Array] # @return [Pin::ProxyType] def inner_process_macro pin, macro, api_map, context, locals - vals = arguments.map{ |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals), source: :chain) } + vals = arguments.map { |c| Pin::ProxyType.anonymous(c.infer(api_map, pin, locals), source: :chain) } txt = macro.tag.text.clone # @sg-ignore Need to add nil check here if txt.empty? && macro.tag.name @@ -260,7 +258,7 @@ def inner_process_macro pin, macro, api_map, context, locals # @param context [ComplexType] # @return [ComplexType, nil] def extra_return_type docstring, context - if docstring.has_tag?('return_single_parameter') #&& context.subtypes.one? + if docstring.has_tag?('return_single_parameter') # && context.subtypes.one? return context.subtypes.first || ComplexType::UNDEFINED elsif docstring.has_tag?('return_value_parameter') && context.value_types.one? return context.value_types.first @@ -270,7 +268,7 @@ def extra_return_type docstring, context # @param name_pin [Pin::Base] # @return [Pin::Method, nil] - def find_method_pin(name_pin) + def find_method_pin name_pin method_pin = name_pin until method_pin.is_a?(Pin::Method) # @sg-ignore Need to support this in flow sensitive typing @@ -287,7 +285,7 @@ def super_pins api_map, name_pin method_pin = find_method_pin(name_pin) return [] if method_pin.nil? pins = api_map.get_method_stack(method_pin.namespace, method_pin.name, scope: method_pin.context.scope) - pins.reject{|p| p.path == name_pin.path} + pins.reject { |p| p.path == name_pin.path } end # @param api_map [ApiMap] @@ -324,7 +322,7 @@ def fix_block_pass # @param block_parameter_types [::Array] # @param locals [::Array] # @return [ComplexType, nil] - def block_symbol_call_type(api_map, context, block_parameter_types, locals) + def block_symbol_call_type api_map, context, block_parameter_types, locals # Ruby's shorthand for sending the passed in method name # to the first yield parameter with no arguments # @sg-ignore Need to add nil check here @@ -342,7 +340,7 @@ def block_symbol_call_type(api_map, context, block_parameter_types, locals) # @param api_map [ApiMap] # @return [Pin::Block, nil] - def find_block_pin(api_map) + def find_block_pin api_map # @sg-ignore Need to add nil check here node_location = Solargraph::Location.from_node(block.node) return if node_location.nil? @@ -353,10 +351,9 @@ def find_block_pin(api_map) # @param api_map [ApiMap] # @param name_pin [Pin::Base] - # @param block_parameter_types [::Array] # @param locals [::Array] # @return [ComplexType, nil] - def block_call_type(api_map, name_pin, locals) + def block_call_type api_map, name_pin, locals return nil unless with_block? block_pin = find_block_pin(api_map) @@ -366,6 +363,14 @@ def block_call_type(api_map, name_pin, locals) # @sg-ignore Need to add nil check here block.infer(api_map, block_pin, locals) end + + protected + + # @sg-ignore Fix "Not enough arguments to Module#protected" + def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array + super + [arguments, block] + end end end end diff --git a/lib/solargraph/source/chain/class_variable.rb b/lib/solargraph/source/chain/class_variable.rb index a804d89e5..f50028ffa 100644 --- a/lib/solargraph/source/chain/class_variable.rb +++ b/lib/solargraph/source/chain/class_variable.rb @@ -5,7 +5,7 @@ class Source class Chain class ClassVariable < Link def resolve api_map, name_pin, locals - api_map.get_class_variable_pins(name_pin.context.namespace).select{|p| p.name == word} + api_map.get_class_variable_pins(name_pin.context.namespace).select { |p| p.name == word } end end end diff --git a/lib/solargraph/source/chain/constant.rb b/lib/solargraph/source/chain/constant.rb index b1c25fab9..e558a2e1f 100644 --- a/lib/solargraph/source/chain/constant.rb +++ b/lib/solargraph/source/chain/constant.rb @@ -6,12 +6,14 @@ class Chain class Constant < Link def initialize word @word = word + + super end def resolve api_map, name_pin, locals return [Pin::ROOT_PIN] if word.empty? if word.start_with?('::') - base = word[2..-1] + base = word[2..] gates = [''] else base = word diff --git a/lib/solargraph/source/chain/global_variable.rb b/lib/solargraph/source/chain/global_variable.rb index 0842803a9..335b8e42c 100644 --- a/lib/solargraph/source/chain/global_variable.rb +++ b/lib/solargraph/source/chain/global_variable.rb @@ -5,7 +5,7 @@ class Source class Chain class GlobalVariable < Link def resolve api_map, name_pin, locals - api_map.get_global_variable_pins.select{|p| p.name == word} + api_map.get_global_variable_pins.select { |p| p.name == word } end end end diff --git a/lib/solargraph/source/chain/hash.rb b/lib/solargraph/source/chain/hash.rb index bf2aa484c..a75963478 100644 --- a/lib/solargraph/source/chain/hash.rb +++ b/lib/solargraph/source/chain/hash.rb @@ -12,12 +12,6 @@ def initialize type, node, splatted = false @splatted = splatted end - # @sg-ignore Fix "Not enough arguments to Module#protected" - protected def equality_fields - # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array - super + [@splatted] - end - def word @word ||= "<#{@type}>" end @@ -29,6 +23,14 @@ def resolve api_map, name_pin, locals def splatted? @splatted end + + protected + + # @sg-ignore Fix "Not enough arguments to Module#protected" + def equality_fields + # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array + super + [@splatted] + end end end end diff --git a/lib/solargraph/source/chain/if.rb b/lib/solargraph/source/chain/if.rb index db0b2481c..186f6e6f0 100644 --- a/lib/solargraph/source/chain/if.rb +++ b/lib/solargraph/source/chain/if.rb @@ -4,25 +4,26 @@ module Solargraph class Source class Chain class If < Link - def word - '' - end - # @param links [::Array] def initialize links + super('') + @links = links end + def resolve api_map, name_pin, locals + types = @links.map { |link| link.infer(api_map, name_pin, locals) } + [Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.try_parse(types.map(&:tag).uniq.join(', ')), + source: :chain)] + end + + protected + # @sg-ignore Fix "Not enough arguments to Module#protected" - protected def equality_fields + def equality_fields # @sg-ignore literal arrays in this module turn into ::Solargraph::Source::Chain::Array super + [@links] end - - def resolve api_map, name_pin, locals - types = @links.map { |link| link.infer(api_map, name_pin, locals) } - [Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.try_parse(types.map(&:tag).uniq.join(', ')), source: :chain)] - end end end end diff --git a/lib/solargraph/source/chain/instance_variable.rb b/lib/solargraph/source/chain/instance_variable.rb index 2cb1a0ef8..60df51bfe 100644 --- a/lib/solargraph/source/chain/instance_variable.rb +++ b/lib/solargraph/source/chain/instance_variable.rb @@ -18,7 +18,9 @@ def initialize word, node, location # type ::Array<::Solargraph::Pin::BaseVariable, ::NilClass> # for Solargraph::Source::Chain::InstanceVariable#resolve def resolve api_map, name_pin, locals - ivars = api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word} + ivars = api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select do |p| + p.name == word + end out = api_map.var_at_location(ivars, word, name_pin, location) [out].compact end diff --git a/lib/solargraph/source/chain/link.rb b/lib/solargraph/source/chain/link.rb index 344f7affd..4eb6c7d5c 100644 --- a/lib/solargraph/source/chain/link.rb +++ b/lib/solargraph/source/chain/link.rb @@ -17,17 +17,6 @@ def initialize word = '' @word = word end - # @sg-ignore two problems - Declared return type - # ::Solargraph::Source::Chain::Array does not match inferred - # type ::Array(::Class<::Solargraph::Source::Chain::Link>, - # ::String) for - # Solargraph::Source::Chain::Link#equality_fields - # and - # Not enough arguments to Module#protected - protected def equality_fields - [self.class, word] - end - def undefined? word == '' end @@ -44,18 +33,12 @@ def resolve api_map, name_pin, locals [] end - # debugging description of contents; not for machine use - # @return [String] - def desc - word - end - def to_s desc end def inspect - "#<#{self.class} - `#{self.desc}`>" + "#<#{self.class} - `#{desc}`>" end def head? @@ -87,14 +70,21 @@ def desc word end - def inspect - "#<#{self.class} - `#{self.desc}`>" - end - include Logging protected + # @sg-ignore two problems - Declared return type + # ::Solargraph::Source::Chain::Array does not match inferred + # type ::Array(::Class<::Solargraph::Source::Chain::Link>, + # ::String) for + # Solargraph::Source::Chain::Link#equality_fields + # and + # Not enough arguments to Module#protected + def equality_fields + [self.class, word] + end + # Mark whether this link is the head of a chain # # @param bool [Boolean] diff --git a/lib/solargraph/source/chain/literal.rb b/lib/solargraph/source/chain/literal.rb index 03c6149c1..e8d9b753c 100644 --- a/lib/solargraph/source/chain/literal.rb +++ b/lib/solargraph/source/chain/literal.rb @@ -6,15 +6,13 @@ module Solargraph class Source class Chain class Literal < Link - def word - @word ||= "<#{@type}>" - end - - attr_reader :value + attr_reader :word, :value # @param type [String] # @param node [Parser::AST::Node, Object] def initialize type, node + super("<#{type}>") + if node.is_a?(::Parser::AST::Node) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check if node.type == :true @@ -23,7 +21,7 @@ def initialize type, node elsif node.type == :false @value = false # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - elsif [:int, :sym].include?(node.type) + elsif %i[int sym].include?(node.type) # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check @value = node.children.first end diff --git a/lib/solargraph/source/chain/or.rb b/lib/solargraph/source/chain/or.rb index 655abe4aa..327d465b7 100644 --- a/lib/solargraph/source/chain/or.rb +++ b/lib/solargraph/source/chain/or.rb @@ -4,14 +4,12 @@ module Solargraph class Source class Chain class Or < Link - def word - '' - end - attr_reader :links # @param links [::Array] def initialize links + super('') + @links = links end diff --git a/lib/solargraph/source/chain/q_call.rb b/lib/solargraph/source/chain/q_call.rb index 811594f7d..7247cf4cc 100644 --- a/lib/solargraph/source/chain/q_call.rb +++ b/lib/solargraph/source/chain/q_call.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph class Source class Chain diff --git a/lib/solargraph/source/chain/variable.rb b/lib/solargraph/source/chain/variable.rb index 975fbf6f5..8bf424e3b 100644 --- a/lib/solargraph/source/chain/variable.rb +++ b/lib/solargraph/source/chain/variable.rb @@ -5,7 +5,9 @@ class Source class Chain class Variable < Link def resolve api_map, name_pin, locals - api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select{|p| p.name == word} + api_map.get_instance_variable_pins(name_pin.context.namespace, name_pin.context.scope).select do |p| + p.name == word + end end end end diff --git a/lib/solargraph/source/chain/z_super.rb b/lib/solargraph/source/chain/z_super.rb index 5b0106c92..f6ef77106 100644 --- a/lib/solargraph/source/chain/z_super.rb +++ b/lib/solargraph/source/chain/z_super.rb @@ -11,9 +11,7 @@ class ZSuper < Call attr_reader :arguments # @param word [String] - # @param arguments [::Array] # @param with_block [Boolean] True if the chain is inside a block - # @param head [Boolean] True if the call is the start of its chain def initialize word, with_block = false super(word, nil, [], with_block) end @@ -22,7 +20,7 @@ def initialize word, with_block = false # @param name_pin [Pin::Base] # @param locals [::Array] def resolve api_map, name_pin, locals - return super_pins(api_map, name_pin) + super_pins(api_map, name_pin) end end end diff --git a/lib/solargraph/source/change.rb b/lib/solargraph/source/change.rb index eb7a550c0..acea51b67 100644 --- a/lib/solargraph/source/change.rb +++ b/lib/solargraph/source/change.rb @@ -28,7 +28,7 @@ def initialize range, new_text # syntax errors will be repaired. # @return [String] The updated text. def write text, nullable = false - if nullable and !range.nil? and new_text.match(/[.\[{(@$:]$/) + if nullable && !range.nil? && new_text.match(/[.\[{(@$:]$/) [':', '@'].each do |dupable| next unless new_text == dupable # @sg-ignore flow sensitive typing needs to handle attrs @@ -66,7 +66,7 @@ def repair text match = result[0, off].match(/[.:]+\z/) if match # @sg-ignore flow sensitive typing should be able to handle redefinition - result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..-1] + result = result[0, off].sub(/#{match[0]}\z/, ' ' * match[0].length) + result[off..] end result end @@ -82,7 +82,7 @@ def commit text, insert start_offset = Position.to_offset(text, range.start) # @sg-ignore Need to add nil check here end_offset = Position.to_offset(text, range.ending) - (start_offset == 0 ? '' : text[0..start_offset-1].to_s) + normalize(insert) + text[end_offset..-1].to_s + (start_offset.zero? ? '' : text[0..(start_offset - 1)].to_s) + normalize(insert) + text[end_offset..].to_s end end end diff --git a/lib/solargraph/source/cursor.rb b/lib/solargraph/source/cursor.rb index 5ee9ac4b8..1afa1c76c 100644 --- a/lib/solargraph/source/cursor.rb +++ b/lib/solargraph/source/cursor.rb @@ -39,11 +39,13 @@ def word # @return [String] def start_of_word @start_of_word ||= begin - match = source.code[0..offset-1].to_s.match(start_word_pattern) + match = source.code[0..(offset - 1)].to_s.match(start_word_pattern) result = (match ? match[0] : '') # Including the preceding colon if the word appears to be a symbol # @sg-ignore Need to add nil check here - result = ":#{result}" if source.code[0..offset-result.length-1].end_with?(':') and !source.code[0..offset-result.length-1].end_with?('::') + if source.code[0..(offset - result.length - 1)].end_with?(':') && !source.code[0..(offset - result.length - 1)].end_with?('::') + result = ":#{result}" + end result end end @@ -55,14 +57,14 @@ def start_of_word # @sg-ignore Need to add nil check here def end_of_word @end_of_word ||= begin - match = source.code[offset..-1].to_s.match(end_word_pattern) + match = source.code[offset..].to_s.match(end_word_pattern) match ? match[0] : '' end end # @return [Boolean] def start_of_constant? - source.code[offset-2, 2] == '::' + source.code[offset - 2, 2] == '::' end # The range of the word at the current position. @@ -126,20 +128,18 @@ def node # @return [Position] def node_position - @node_position ||= begin - if start_of_word.empty? - # @sg-ignore Need to add nil check here - match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) - if match - # @sg-ignore Need to add nil check here - Position.from_offset(source.code, offset - match[0].length) - else - position - end - else - position - end - end + @node_position ||= if start_of_word.empty? + # @sg-ignore Need to add nil check here + match = source.code[0, offset].match(/\s*(\.|:+)\s*$/) + if match + # @sg-ignore Need to add nil check here + Position.from_offset(source.code, offset - match[0].length) + else + position + end + else + position + end end # @return [Parser::AST::Node, nil] diff --git a/lib/solargraph/source/encoding_fixes.rb b/lib/solargraph/source/encoding_fixes.rb index 2ed70037c..0bb8d7f7e 100644 --- a/lib/solargraph/source/encoding_fixes.rb +++ b/lib/solargraph/source/encoding_fixes.rb @@ -10,13 +10,12 @@ module EncodingFixes # @param string [String] # @return [String] def normalize string - begin - string.dup.force_encoding('UTF-8') - rescue ::Encoding::CompatibilityError, ::Encoding::UndefinedConversionError, ::Encoding::InvalidByteSequenceError => e - # @todo Improve error handling - Solargraph::Logging.logger.warn "Normalize error: #{e.message}" - string - end + string.dup.force_encoding('UTF-8') + rescue ::Encoding::CompatibilityError, ::Encoding::UndefinedConversionError, + ::Encoding::InvalidByteSequenceError => e + # @todo Improve error handling + Solargraph::Logging.logger.warn "Normalize error: #{e.message}" + string end end end diff --git a/lib/solargraph/source/source_chainer.rb b/lib/solargraph/source/source_chainer.rb index aeffbeeec..b410e0214 100644 --- a/lib/solargraph/source/source_chainer.rb +++ b/lib/solargraph/source/source_chainer.rb @@ -32,10 +32,22 @@ def initialize source, position # @return [Source::Chain] def chain # Special handling for files that end with an integer and a period - return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])), Chain::UNDEFINED_CALL]) if phrase =~ /^[0-9]+\.$/ - # @sg-ignore Need to add nil check here - return Chain.new([Chain::Literal.new('Symbol', phrase[1..].to_sym)]) if phrase.start_with?(':') && !phrase.start_with?('::') - return SourceChainer.chain(source, Position.new(position.line, position.character + 1)) if end_of_phrase.strip == '::' && source.code[Position.to_offset(source.code, position)].to_s.match?(/[a-z]/i) + if phrase =~ /^[0-9]+\.$/ + return Chain.new([Chain::Literal.new('Integer', Integer(phrase[0..-2])), + Chain::UNDEFINED_CALL]) + end + if phrase.start_with?(':') && !phrase.start_with?('::') + return Chain.new([Chain::Literal.new('Symbol', + # @sg-ignore Need to add nil check here + phrase[1..].to_sym)]) + end + if end_of_phrase.strip == '::' && source.code[Position.to_offset( + source.code, position + )].to_s.match?(/[a-z]/i) + return SourceChainer.chain(source, + Position.new(position.line, + position.character + 1)) + end begin return Chain.new([]) if phrase.end_with?('..') # @type [::Parser::AST::Node, nil] @@ -52,7 +64,12 @@ def chain elsif source.repaired? node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) else - node, parent = source.tree_at(fixed_position.line, fixed_position.column)[0..2] unless source.error_ranges.any?{|r| r.nil? || r.include?(fixed_position)} + unless source.error_ranges.any? do |r| + r.nil? || r.include?(fixed_position) + end + node, parent = source.tree_at(fixed_position.line, + fixed_position.column)[0..2] + end # Exception for positions that chain literal nodes in unsynchronized sources node = nil unless source.synchronized? || !Parser.infer_literal_node_type(node).nil? node = Parser.parse(fixed_phrase, source.filename, fixed_position.line) if node.nil? @@ -86,13 +103,13 @@ def chain # @sg-ignore Need to add nil check here # @return [String] def phrase - @phrase ||= source.code[signature_data..offset-1] + @phrase ||= source.code[signature_data..(offset - 1)] end # @sg-ignore Need to add nil check here # @return [String] def fixed_phrase - @fixed_phrase ||= phrase[0..-(end_of_phrase.length+1)] + @fixed_phrase ||= phrase[0..-(end_of_phrase.length + 1)] end # @return [Position] @@ -144,50 +161,47 @@ def get_signature_data_at index brackets = 0 squares = 0 parens = 0 - index -=1 + index -= 1 in_whitespace = false while index >= 0 pos = Position.from_offset(@source.code, index) - break if index > 0 and @source.comment_at?(pos) - break if brackets > 0 or parens > 0 or squares > 0 + break if index.positive? && @source.comment_at?(pos) + break if brackets.positive? || parens.positive? || squares.positive? char = @source.code[index, 1] break if char.nil? # @todo Is this the right way to handle this? - if brackets.zero? and parens.zero? and squares.zero? and [' ', "\r", "\n", "\t"].include?(char) + if brackets.zero? && parens.zero? && squares.zero? && [' ', "\r", "\n", "\t"].include?(char) in_whitespace = true else - if brackets.zero? and parens.zero? and squares.zero? and in_whitespace + # @sg-ignore Need to add nil check here + if brackets.zero? && parens.zero? && squares.zero? && in_whitespace && !((char == '.') || @source.code[(index + 1)..].strip.start_with?('.')) + @source.code[(index + 1)..] # @sg-ignore Need to add nil check here - unless char == '.' or @source.code[index+1..-1].strip.start_with?('.') - old = @source.code[index+1..-1] - # @sg-ignore Need to add nil check here - nxt = @source.code[index+1..-1].lstrip - # @sg-ignore Need to add nil check here - index += (@source.code[index+1..-1].length - @source.code[index+1..-1].lstrip.length) - break - end + @source.code[(index + 1)..].lstrip + # @sg-ignore Need to add nil check here + index += (@source.code[(index + 1)..].length - @source.code[(index + 1)..].lstrip.length) + break end - if char == ')' - parens -=1 - elsif char == ']' - squares -=1 - elsif char == '}' + case char + when ')' + parens -= 1 + when ']' + squares -= 1 + when '}' brackets -= 1 - elsif char == '(' + when '(' parens += 1 - elsif char == '{' + when '{' brackets += 1 - elsif char == '[' + when '[' squares += 1 end - if brackets.zero? and parens.zero? and squares.zero? + if brackets.zero? && parens.zero? && squares.zero? break if ['"', "'", ',', ';', '%'].include?(char) break if ['!', '?'].include?(char) && index < offset - 1 break if char == '$' if char == '@' index -= 1 - if @source.code[index, 1] == '@' - index -= 1 - end + index -= 1 if @source.code[index, 1] == '@' break end elsif parens == 1 || brackets == 1 || squares == 1 diff --git a/lib/solargraph/source/updater.rb b/lib/solargraph/source/updater.rb index 4fdb78330..9debc0283 100644 --- a/lib/solargraph/source/updater.rb +++ b/lib/solargraph/source/updater.rb @@ -33,7 +33,7 @@ def initialize filename, version, changes # @return [String] def write text, nullable = false can_nullify = (nullable and changes.length == 1) - return @output if @input == text and can_nullify == @did_nullify + return @output if (@input == text) && (can_nullify == @did_nullify) @input = text @output = text @did_nullify = can_nullify diff --git a/lib/solargraph/source_map.rb b/lib/solargraph/source_map.rb index 714970f21..18f623993 100644 --- a/lib/solargraph/source_map.rb +++ b/lib/solargraph/source_map.rb @@ -62,7 +62,9 @@ def pins_by_class klass # # @return [Integer] def api_hash - @api_hash ||= (pins_by_class(Pin::Constant) + pins_by_class(Pin::Namespace).select { |pin| pin.namespace.to_s > '' } + pins_by_class(Pin::Reference) + pins_by_class(Pin::Method).map(&:node) + locals).hash + @api_hash ||= (pins_by_class(Pin::Constant) + pins_by_class(Pin::Namespace).select do |pin| + pin.namespace.to_s > '' + end + pins_by_class(Pin::Reference) + pins_by_class(Pin::Method).map(&:node) + locals).hash end # @return [String, nil] @@ -144,7 +146,7 @@ def references name # @param location [Location] # @return [Array] - def locals_at(location) + def locals_at location return [] if location.filename != filename closure = locate_closure_pin(location.range.start.line, location.range.start.character) locals.select { |pin| pin.visible_at?(closure, location) } @@ -208,8 +210,10 @@ def _locate_pin line, character, *klasses # @todo Attribute pins should not be treated like closures, but # there's probably a better way to handle it next if pin.is_a?(Pin::Method) && pin.attribute? - # @sg-ignore Need to add nil check here - found = pin if (klasses.empty? || klasses.any? { |kls| pin.is_a?(kls) } ) && pin.location.range.contain?(position) + found = pin if (klasses.empty? || klasses.any? do |kls| + pin.is_a?(kls) + # @sg-ignore Need to add nil check here + end) && pin.location.range.contain?(position) # @sg-ignore Need to add nil check here break if pin.location.range.start.line > line end diff --git a/lib/solargraph/source_map/clip.rb b/lib/solargraph/source_map/clip.rb index b2b4d2b5d..c1e7c24af 100644 --- a/lib/solargraph/source_map/clip.rb +++ b/lib/solargraph/source_map/clip.rb @@ -13,7 +13,9 @@ def initialize api_map, cursor @cursor = cursor closure_pin = closure # @sg-ignore Need to add nil check here - closure_pin.rebind(api_map) if closure_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(closure_pin.receiver).contain?(cursor.range.start) + if closure_pin.is_a?(Pin::Block) && !Solargraph::Range.from_node(closure_pin.receiver).contain?(cursor.range.start) + closure_pin.rebind(api_map) + end end # @return [Array] Relevant pins for infering the type of the Cursor's position @@ -21,8 +23,12 @@ def define return [] if cursor.comment? || cursor.chain.literal? result = cursor.chain.define(api_map, closure, locals) result.concat file_global_methods - # @sg-ignore Need to add nil check here - result.concat((source_map.pins + source_map.locals).select{ |p| p.name == cursor.word && p.location.range.contain?(cursor.position) }) if result.empty? + if result.empty? + result.concat((source_map.pins + source_map.locals).select do |p| + # @sg-ignore Need to add nil check here + p.name == cursor.word && p.location.range.contain?(cursor.position) + end) + end result end @@ -34,7 +40,9 @@ def types # @return [Completion] def complete return package_completions([]) if !source_map.source.parsed? || cursor.string? - return package_completions(api_map.get_symbols) if cursor.chain.literal? && cursor.chain.links.last.word == '' + if cursor.chain.literal? && cursor.chain.links.last.word == '' + return package_completions(api_map.get_symbols) + end return Completion.new([], cursor.range) if cursor.chain.literal? if cursor.comment? tag_complete @@ -128,12 +136,11 @@ def complete_keyword_parameters next unless param.keyword? result.push Pin::KeywordParam.new(pin.location, "#{param.name}:") end - if !pin.parameters.empty? && pin.parameters.last.kwrestarg? - pin.docstring.tags(:param).each do |tag| - next if done.include?(tag.name) - done.push tag.name - result.push Pin::KeywordParam.new(pin.location, "#{tag.name}:") - end + next unless !pin.parameters.empty? && pin.parameters.last.kwrestarg? + pin.docstring.tags(:param).each do |tag| + next if done.include?(tag.name) + done.push tag.name + result.push Pin::KeywordParam.new(pin.location, "#{tag.name}:") end end result @@ -143,10 +150,10 @@ def complete_keyword_parameters # @return [Completion] def package_completions result frag_start = cursor.start_of_word.to_s.downcase - filtered = result.uniq(&:name).select { |s| + filtered = result.uniq(&:name).select do |s| s.name.downcase.start_with?(frag_start) && - (!s.is_a?(Pin::Method) || s.name.match(/^[a-z0-9_]+(\!|\?|=)?$/i)) - } + (!s.is_a?(Pin::Method) || s.name.match(/^[a-z0-9_]+(!|\?|=)?$/i)) + end Completion.new(filtered, cursor.range) end @@ -154,7 +161,7 @@ def package_completions result def tag_complete result = [] # @sg-ignore Need to add nil check here - match = source_map.code[0..cursor.offset-1].match(/[\[<, ]([a-z0-9_:]*)\z/i) + match = source_map.code[0..(cursor.offset - 1)].match(/[\[<, ]([a-z0-9_:]*)\z/i) if match # @sg-ignore Need to add nil check here full = match[1] @@ -170,7 +177,7 @@ def tag_complete end else # @sg-ignore Need to add nil check here - result.concat api_map.get_constants('', full.end_with?('::') ? '' : context_pin.full_context.namespace, *gates) #.select { |pin| pin.name.start_with?(full) } + result.concat api_map.get_constants('', full.end_with?('::') ? '' : context_pin.full_context.namespace, *gates) # .select { |pin| pin.name.start_with?(full) } end end package_completions(result) @@ -183,25 +190,24 @@ def code_complete if cursor.chain.constant? || cursor.start_of_constant? full = cursor.chain.links.first.word type = if cursor.chain.undefined? - cursor.chain.base.infer(api_map, context_pin, locals) - else - if full.include?('::') && cursor.chain.links.length == 1 - # @sg-ignore Need to add nil check here - ComplexType.try_parse(full.split('::')[0..-2].join('::')) - elsif cursor.chain.links.length > 1 - ComplexType.try_parse(full) - else - ComplexType::UNDEFINED - end - end + cursor.chain.base.infer(api_map, context_pin, locals) + elsif full.include?('::') && cursor.chain.links.length == 1 + # @sg-ignore Need to add nil check here + ComplexType.try_parse(full.split('::')[0..-2].join('::')) + elsif cursor.chain.links.length > 1 + ComplexType.try_parse(full) + else + ComplexType::UNDEFINED + end if type.undefined? if full.include?('::') result.concat api_map.get_constants(full, *gates) else - result.concat api_map.get_constants('', cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) #.select { |pin| pin.name.start_with?(full) } + result.concat api_map.get_constants('', cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) # .select { |pin| pin.name.start_with?(full) } end else - result.concat api_map.get_constants(type.namespace, cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) + result.concat api_map.get_constants(type.namespace, + cursor.start_of_constant? ? '' : context_pin.full_context.namespace, *gates) end else type = cursor.chain.base.infer(api_map, closure, locals) @@ -210,14 +216,16 @@ def code_complete if cursor.word.start_with?('@@') return package_completions(api_map.get_class_variable_pins(context_pin.full_context.namespace)) elsif cursor.word.start_with?('@') - return package_completions(api_map.get_instance_variable_pins(closure.full_context.namespace, closure.context.scope)) + return package_completions(api_map.get_instance_variable_pins(closure.full_context.namespace, + closure.context.scope)) elsif cursor.word.start_with?('$') return package_completions(api_map.get_global_variable_pins) end result.concat locals result.concat file_global_methods unless closure.binder.namespace.empty? result.concat api_map.get_constants(context_pin.context.namespace, *gates) - result.concat api_map.get_methods(closure.binder.namespace, scope: closure.binder.scope, visibility: [:public, :private, :protected]) + result.concat api_map.get_methods(closure.binder.namespace, scope: closure.binder.scope, + visibility: %i[public private protected]) result.concat api_map.get_methods('Kernel') result.concat api_map.keyword_pins.to_a end diff --git a/lib/solargraph/source_map/mapper.rb b/lib/solargraph/source_map/mapper.rb index e6b8f6878..8c306473d 100644 --- a/lib/solargraph/source_map/mapper.rb +++ b/lib/solargraph/source_map/mapper.rb @@ -12,7 +12,7 @@ class Mapper private_class_method :new - DIRECTIVE_REGEXP = /(@\!method|@\!attribute|@\!visibility|@\!domain|@\!macro|@\!parse|@\!override)/.freeze + DIRECTIVE_REGEXP = /(@!method|@!attribute|@!visibility|@!domain|@!macro|@!parse|@!override)/ # Generate the data. # @@ -29,10 +29,10 @@ def map source @locals.each { |l| l.source = :code } process_comment_directives [@pins, @locals] - # rescue Exception => e - # Solargraph.logger.warn "Error mapping #{source.filename}: [#{e.class}] #{e.message}" - # Solargraph.logger.warn e.backtrace.join("\n") - # [[], []] + # rescue Exception => e + # Solargraph.logger.warn "Error mapping #{source.filename}: [#{e.class}] #{e.message}" + # Solargraph.logger.warn e.backtrace.join("\n") + # [[], []] end # @param filename [String] @@ -63,9 +63,9 @@ def pins # @param position [Solargraph::Position] # @return [Solargraph::Pin::Closure] - def closure_at(position) + def closure_at position # @sg-ignore Need to add nil check here - pins.select{|pin| pin.is_a?(Pin::Closure) and pin.location.range.contain?(position)}.last + pins.select { |pin| pin.is_a?(Pin::Closure) and pin.location.range.contain?(position) }.last end # @param source_position [Position] @@ -94,7 +94,7 @@ def find_directive_line_number comment, tag, start # Avoid overruning the index return start unless start < comment.lines.length # @sg-ignore Need to add nil check here - num = comment.lines[start..-1].find_index do |line| + num = comment.lines[start..].find_index do |line| # Legacy method directives might be `@method` instead of `@!method` # @todo Legacy syntax should probably emit a warning line.include?("@!#{tag}") || (tag == 'method' && line.include?("@#{tag}")) @@ -114,11 +114,8 @@ def process_directive source_position, comment_position, directive case directive.tag.tag_name when 'method' namespace = closure_at(source_position) || @pins.first - # @todo Missed nil violation - # @todo Need to add nil check here - if namespace.location.range.start.line < comment_position.line - namespace = closure_at(comment_position) - end + # @sg-ignore Need to add nil check here + namespace = closure_at(comment_position) if namespace.location.range.start.line < comment_position.line begin src = Solargraph::Source.load_string("def #{directive.tag.name};end", @source.filename) region = Parser::Region.new(source: src, closure: namespace) @@ -128,19 +125,20 @@ def process_directive source_position, comment_position, directive return if gen_pin.nil? # Move the location to the end of the line so it gets recognized # as originating from a comment - shifted = Solargraph::Position.new(comment_position.line, @code.lines[comment_position.line].to_s.chomp.length) + shifted = Solargraph::Position.new(comment_position.line, + @code.lines[comment_position.line].to_s.chomp.length) # @todo: Smelly instance variable access gen_pin.instance_variable_set(:@comments, docstring.all.to_s) gen_pin.instance_variable_set(:@location, Solargraph::Location.new(@filename, Range.new(shifted, shifted))) gen_pin.instance_variable_set(:@explicit, false) @pins.push gen_pin - rescue Parser::SyntaxError => e + rescue Parser::SyntaxError # @todo Handle error in directive end when 'attribute' return if directive.tag.name.nil? namespace = closure_at(source_position) - t = (directive.tag.types.nil? || directive.tag.types.empty?) ? nil : directive.tag.types.flatten.join('') + t = directive.tag.types.nil? || directive.tag.types.empty? ? nil : directive.tag.types.join if t.nil? || t.include?('r') pins.push Solargraph::Pin::Method.new( location: location, @@ -166,34 +164,35 @@ def process_directive source_position, comment_position, directive source: :source_map ) pins.push method_pin - method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last, source: :source_map) + method_pin.parameters.push Pin::Parameter.new(name: 'value', decl: :arg, closure: pins.last, + source: :source_map) if pins.last.return_type.defined? - pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), 'value') + pins.last.docstring.add_tag YARD::Tags::Tag.new(:param, '', pins.last.return_type.to_s.split(', '), + 'value') end end when 'visibility' - kind = directive.tag.text&.to_sym - # @sg-ignore Need to look at Tuple#include? handling - return unless [:private, :protected, :public].include?(kind) + kind = directive.tag.text&.to_sym + # @sg-ignore Need to look at Tuple#include? handling + return unless %i[private protected public].include?(kind) - name = directive.tag.name - closure = closure_at(source_position) || @pins.first - # @todo Missed nil violation - # @todo Need to add nil check here - if closure.location.range.start.line < comment_position.line - closure = closure_at(comment_position) + name = directive.tag.name + closure = closure_at(source_position) || @pins.first + # @sg-ignore Need to add nil check here + closure = closure_at(comment_position) if closure.location.range.start.line < comment_position.line + if closure.is_a?(Pin::Method) && no_empty_lines?(comment_position.line, source_position.line) + # @todo Smelly instance variable access + closure.instance_variable_set(:@visibility, kind) + else + matches = pins.select do |pin| + pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == namespace && pin.context.scope == namespace.is_a?(Pin::Singleton) ? :class : :instance end - if closure.is_a?(Pin::Method) && no_empty_lines?(comment_position.line, source_position.line) + matches.each do |pin| # @todo Smelly instance variable access - closure.instance_variable_set(:@visibility, kind) - else - matches = pins.select{ |pin| pin.is_a?(Pin::Method) && pin.name == name && pin.namespace == namespace && pin.context.scope == namespace.is_a?(Pin::Singleton) ? :class : :instance } - matches.each do |pin| - # @todo Smelly instance variable access - pin.instance_variable_set(:@visibility, kind) - end + pin.instance_variable_set(:@visibility, kind) end + end when 'parse' begin @@ -204,21 +203,21 @@ def process_directive source_position, comment_position, directive # @todo These pins may need to be marked not explicit index = @pins.length loff = if @code.lines[comment_position.line].strip.end_with?('@!parse') - comment_position.line + 1 - else - comment_position.line - end + comment_position.line + 1 + else + comment_position.line + end locals = [] ivars = [] Parser.process_node(src.node, region, @pins, locals, ivars) @pins.concat ivars # @sg-ignore Need to add nil check here - @pins[index..-1].each do |p| + @pins[index..].each do |p| # @todo Smelly instance variable access p.location.range.start.instance_variable_set(:@line, p.location.range.start.line + loff) p.location.range.ending.instance_variable_set(:@line, p.location.range.ending.line + loff) end - rescue Parser::SyntaxError => e + rescue Parser::SyntaxError # @todo Handle parser errors in !parse directives end when 'domain' @@ -237,7 +236,7 @@ def process_directive source_position, comment_position, directive # @param line1 [Integer] # @param line2 [Integer] # @sg-ignore Need to add nil check here - def no_empty_lines?(line1, line2) + def no_empty_lines? line1, line2 # @sg-ignore Need to add nil check here @code.lines[line1..line2].none? { |line| line.strip.empty? } end @@ -248,7 +247,7 @@ def remove_inline_comment_hashes comment ctxt = '' num = nil started = false - comment.lines.each { |l| + comment.lines.each do |l| # Trim the comment and minimum leading whitespace p = l.encode('UTF-8', invalid: :replace, replace: '?').gsub(/^#+/, '') if num.nil? && !p.strip.empty? @@ -259,8 +258,8 @@ def remove_inline_comment_hashes comment # @sg-ignore Need to add nil check here num = cur if cur < num end - ctxt += "#{p[num..-1]}" if started - } + ctxt += p[num..].to_s if started + end ctxt end @@ -269,7 +268,14 @@ def process_comment_directives return unless @code.encode('UTF-8', invalid: :replace, replace: '?') =~ DIRECTIVE_REGEXP code_lines = @code.lines @source.associated_comments.each do |line, comments| - src_pos = line ? Position.new(line, code_lines[line].to_s.chomp.index(/[^\s]/) || 0) : Position.new(code_lines.length, 0) + src_pos = if line + Position.new(line, + code_lines[line].to_s.chomp.index(/[^\s]/) || 0) + else + Position.new( + code_lines.length, 0 + ) + end # @sg-ignore Need to add nil check here com_pos = Position.new(line + 1 - comments.lines.length, 0) process_comment(src_pos, com_pos, comments) diff --git a/lib/solargraph/type_checker.rb b/lib/solargraph/type_checker.rb index fb76ba7b5..724340821 100644 --- a/lib/solargraph/type_checker.rb +++ b/lib/solargraph/type_checker.rb @@ -52,26 +52,26 @@ def source # @param inferred [ComplexType, ComplexType::UniqueType] # @param expected [ComplexType, ComplexType::UniqueType] - def return_type_conforms_to?(inferred, expected) + def return_type_conforms_to? inferred, expected conforms_to?(inferred, expected, :return_type) end # @param inferred [ComplexType, ComplexType::UniqueType] # @param expected [ComplexType, ComplexType::UniqueType] - def arg_conforms_to?(inferred, expected) + def arg_conforms_to? inferred, expected conforms_to?(inferred, expected, :method_call) end # @param inferred [ComplexType, ComplexType::UniqueType] # @param expected [ComplexType, ComplexType::UniqueType] - def assignment_conforms_to?(inferred, expected) + def assignment_conforms_to? inferred, expected conforms_to?(inferred, expected, :assignment) end # @param inferred [ComplexType, ComplexType::UniqueType] # @param expected [ComplexType, ComplexType::UniqueType] # @param scenario [Symbol] - def conforms_to?(inferred, expected, scenario) + def conforms_to? inferred, expected, scenario rules_arr = [] rules_arr << :allow_empty_params unless rules.require_inferred_type_params? rules_arr << :allow_any_match unless rules.require_all_unique_types_match_expected? @@ -86,12 +86,12 @@ def conforms_to?(inferred, expected, scenario) # @return [Array] def problems @problems ||= begin - all = method_tag_problems - .concat(variable_type_tag_problems) - .concat(const_problems) - .concat(call_problems) - unignored = without_ignored(all) - unignored.concat(unneeded_sgignore_problems) + all = method_tag_problems + .concat(variable_type_tag_problems) + .concat(const_problems) + .concat(call_problems) + unignored = without_ignored(all) + unignored.concat(unneeded_sgignore_problems) end end @@ -148,7 +148,10 @@ def method_return_type_problems_for pin if pin.return_type.undefined? && rules.require_type_tags? if pin.attribute? inferred = pin.probe(api_map).self_to_type(pin.full_context) - result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin) unless inferred.defined? + unless inferred.defined? + result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", + pin: pin) + end else result.push Problem.new(pin.location, "Missing @return tag for #{pin.path}", pin: pin) end @@ -167,7 +170,8 @@ def method_return_type_problems_for pin end else unless return_type_conforms_to?(inferred, declared) - result.push Problem.new(pin.location, "Declared return type #{declared.rooted_tags} does not match inferred type #{inferred.rooted_tags} for #{pin.path}", pin: pin) + result.push Problem.new(pin.location, + "Declared return type #{declared.rooted_tags} does not match inferred type #{inferred.rooted_tags} for #{pin.path}", pin: pin) end end end @@ -183,7 +187,7 @@ def method_return_type_problems_for pin def resolved_constant? pin return true if pin.typify(api_map).defined? constant_pins = api_map.get_constants('', *pin.closure.gates) - .select { |p| p.name == pin.return_type.namespace } + .select { |p| p.name == pin.return_type.namespace } return true if constant_pins.find { |p| p.typify(api_map).defined? } # will need to probe when a constant name is assigned to a # class/module (alias) @@ -205,19 +209,19 @@ def method_param_type_problems_for pin pin.signatures.each do |sig| params = param_details_from_stack(sig, stack) if rules.require_type_tags? - sig.parameters.each do |par| - break if par.decl == :restarg || par.decl == :kwrestarg || par.decl == :blockarg - unless params[par.name] - if pin.attribute? - inferred = pin.probe(api_map).self_to_type(pin.full_context) - if inferred.undefined? - result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin) - end - else + sig.parameters.each do |par| + break if %i[restarg kwrestarg blockarg].include?(par.decl) + unless params[par.name] + if pin.attribute? + inferred = pin.probe(api_map).self_to_type(pin.full_context) + if inferred.undefined? result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin) end + else + result.push Problem.new(pin.location, "Missing @param tag for #{par.name} on #{pin.path}", pin: pin) end end + end end # @param name [String] # @param data [Hash{Symbol => BasicObject}] @@ -225,7 +229,8 @@ def method_param_type_problems_for pin # @type [ComplexType] type = data[:qualified] if type.undefined? - result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", pin: pin) + result.push Problem.new(pin.location, "Unresolved type #{data[:tagged]} for #{name} param on #{pin.path}", + pin: pin) end end end @@ -257,20 +262,20 @@ def variable_type_tag_problems end else unless assignment_conforms_to?(inferred, declared) - result.push Problem.new(pin.location, "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin) + result.push Problem.new(pin.location, + "Declared type #{declared} does not match inferred type #{inferred} for variable #{pin.name}", pin: pin) end end elsif declared_externally?(pin) ignored_pins.push pin end elsif !pin.is_a?(Pin::Parameter) && !resolved_constant?(pin) - result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", pin: pin) + result.push Problem.new(pin.location, "Unresolved type #{pin.return_type} for variable #{pin.name}", + pin: pin) end elsif pin.assignment inferred = pin.probe(api_map) - if inferred.undefined? && declared_externally?(pin) - ignored_pins.push pin - end + ignored_pins.push pin if inferred.undefined? && declared_externally?(pin) end end result @@ -313,7 +318,6 @@ def call_problems chain = Solargraph::Parser.chain(call, filename) # @sg-ignore Need to add nil check here closure_pin = source_map.locate_closure_pin(rng.start.line, rng.start.column) - namespace_pin = closure_pin if call.type == :block # blocks in the AST include the method call as well, so the # node returned by #call_nodes_from needs to be backed out @@ -336,7 +340,6 @@ def call_problems found = nil # @type [Array] all_found = [] - closest = ComplexType::UNDEFINED until base.links.first.undefined? # @sg-ignore Need to add nil check here all_found = base.define(api_map, closure_pin, locals) @@ -348,16 +351,14 @@ def call_problems all_closest = all_found.map { |pin| pin.typify(api_map) } closest = ComplexType.new(all_closest.flat_map(&:items).uniq) # @todo remove the internal_or_core? check at a higher-than-strict level - if !found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found)) - # @sg-ignore Need to add nil check here - unless closest.generic? || ignored_pins.include?(found) - if closest.defined? - result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}") - else - result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}") - end - @marked_ranges.push rng + # @sg-ignore Need to add nil check here + if (!found || found.is_a?(Pin::BaseVariable) || (closest.defined? && internal_or_core?(found))) && !(closest.generic? || ignored_pins.include?(found)) + if closest.defined? + result.push Problem.new(location, "Unresolved call to #{missing.links.last.word} on #{closest}") + else + result.push Problem.new(location, "Unresolved call to #{missing.links.last.word}") end + @marked_ranges.push rng end end # @sg-ignore Need to add nil check here @@ -407,7 +408,7 @@ def argument_problems_for chain, api_map, closure_pin, locals, location return [] if !rules.validate_calls? || base.links.first.is_a?(Solargraph::Source::Chain::ZSuper) all_errors = [] - pin.signatures.sort { |sig| sig.parameters.length }.each do |sig| + pin.signatures.sort_by { |sig| sig.parameters.length }.each do |sig| params = param_details_from_stack(sig, pins) signature_errors = signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin @@ -431,7 +432,6 @@ def argument_problems_for chain, api_map, closure_pin, locals, location # @param arguments [Array] # @param sig [Pin::Signature] # @param pin [Pin::Method] - # @param pins [Array] # # @return [Array] def signature_argument_problems_for location, locals, closure_pin, params, arguments, sig, pin @@ -441,31 +441,25 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum # when possible, and when not, ensure provably # incorrect situations are detected. sig.parameters.each_with_index do |par, idx| - return errors if par.decl == :restarg # bail out and assume the rest is valid pending better arg processing + return errors if par.decl == :restarg # bail out and assume the rest is valid pending better arg processing argchain = arguments[idx] if argchain.nil? + final_arg = arguments.last if par.decl == :arg - final_arg = arguments.last if final_arg && final_arg.node.type == :splat argchain = final_arg return errors else errors.push Problem.new(location, "Not enough arguments to #{pin.path}") end - else - final_arg = arguments.last - argchain = final_arg if final_arg && [:kwsplat, :hash].include?(final_arg.node.type) + elsif final_arg && %i[kwsplat hash].include?(final_arg.node.type) + argchain = final_arg end end if argchain - if par.decl != :arg - errors.concat kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx - next - else - if argchain.node.type == :splat && argchain == arguments.last - final_arg = argchain - end - if (final_arg && final_arg.node.type == :splat) + if par.decl == :arg + final_arg = argchain if argchain.node.type == :splat && argchain == arguments.last + if final_arg && final_arg.node.type == :splat # The final argument given has been seen and was a # splat, which doesn't give us useful types or # arities against positional parameters, so let's @@ -490,10 +484,14 @@ def signature_argument_problems_for location, locals, closure_pin, params, argum argtype = argchain.infer(api_map, closure_pin, locals) argtype = argtype.self_to_type(closure_pin.context) if argtype.defined? && ptype.defined? && !arg_conforms_to?(argtype, ptype) - errors.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") + errors.push Problem.new(location, + "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") return errors end end + else + errors.concat kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pin, params, idx + next end elsif par.decl == :kwarg errors.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") @@ -522,27 +520,26 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi argchain = kwargs[par.name.to_sym] if par.decl == :kwrestarg || (par.decl == :optarg && idx == pin.parameters.length - 1 && par.asgn_code == '{}') result.concat kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs) - else - if argchain - data = params[par.name] - if data.nil? - # @todo Some level (strong, I guess) should require the param here - else - # @type [ComplexType, ComplexType::UniqueType] - ptype = data[:qualified] - ptype = ptype.self_to_type(pin.context) - unless ptype.undefined? - # @type [ComplexType] - argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context) - # @todo Unresolved call to defined? - if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) - result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") - end + elsif argchain + data = params[par.name] + if data.nil? + # @todo Some level (strong, I guess) should require the param here + else + # @type [ComplexType, ComplexType::UniqueType] + ptype = data[:qualified] + ptype = ptype.self_to_type(pin.context) + unless ptype.undefined? + # @type [ComplexType] + argtype = argchain.infer(api_map, closure_pin, locals).self_to_type(closure_pin.context) + # @todo Unresolved call to defined? + if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) + result.push Problem.new(location, + "Wrong argument type for #{pin.path}: #{par.name} expected #{ptype}, received #{argtype}") end end - elsif par.decl == :kwarg - result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") end + elsif par.decl == :kwarg + result.push Problem.new(location, "Call to #{pin.path} is missing keyword argument #{par.name}") end result end @@ -552,20 +549,22 @@ def kwarg_problems_for sig, argchain, api_map, closure_pin, locals, location, pi # @param locals [Array] # @param location [Location] # @param pin [Pin::Method] - # @param params [Hash{String => [nil, Hash]}] + # @param params [Hash{String => nil, Hash}] # @param kwargs [Hash{Symbol => Source::Chain}] # @return [Array] - def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, kwargs) + def kwrestarg_problems_for api_map, closure_pin, locals, location, pin, params, kwargs result = [] kwargs.each_pair do |pname, argchain| next unless params.key?(pname.to_s) + # @sg-ignore # @type [ComplexType] - ptype = params[pname.to_s][:qualified] - ptype = ptype.self_to_type(pin.context) + raw_ptype = params[pname.to_s][:qualified] + ptype = raw_ptype.self_to_type(pin.context) argtype = argchain.infer(api_map, closure_pin, locals) argtype = argtype.self_to_type(closure_pin.context) if argtype.defined? && ptype && !arg_conforms_to?(argtype, ptype) - result.push Problem.new(location, "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}") + result.push Problem.new(location, + "Wrong argument type for #{pin.path}: #{pname} expected #{ptype}, received #{argtype}") end end result @@ -575,7 +574,7 @@ def kwrestarg_problems_for(api_map, closure_pin, locals, location, pin, params, # @param pin [Pin::Method, Pin::Signature] # @param relevant_pin [Pin::Method, Pin::Signature] the pin which is under inspection # @return [void] - def add_restkwarg_param_tag_details(param_details, pin, relevant_pin) + def add_restkwarg_param_tag_details param_details, pin, relevant_pin # see if we have additional tags to pay attention to from YARD - # e.g., kwargs in a **restkwargs splat tags = pin.docstring.tags(:param) @@ -596,7 +595,7 @@ def add_restkwarg_param_tag_details(param_details, pin, relevant_pin) # @param pin [Pin::Signature] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] - def signature_param_details(pin) + def signature_param_details pin # @type [Hash{String => Hash{Symbol => String, ComplexType}}] result = {} pin.parameters.each do |param| @@ -630,7 +629,7 @@ def signature_param_details(pin) # @param new_param_details [Hash{String => Hash{Symbol => String, ComplexType}}] # # @return [void] - def add_to_param_details(param_details, param_names, new_param_details) + def add_to_param_details param_details, param_names, new_param_details new_param_details.each do |param_name, details| next unless param_names.include?(param_name) @@ -643,7 +642,7 @@ def add_to_param_details(param_details, param_names, new_param_details) # @param signature [Pin::Signature] # @param method_pin_stack [Array] # @return [Hash{String => Hash{Symbol => String, ComplexType}}] - def param_details_from_stack(signature, method_pin_stack) + def param_details_from_stack signature, method_pin_stack signature_type = signature.typify(api_map) signature = signature.proxy signature_type param_details = signature_param_details(signature) @@ -683,7 +682,7 @@ def external? pin # @param pin [Pin::BaseVariable] def declared_externally? pin - raise "No assignment found" if pin.assignment.nil? + raise 'No assignment found' if pin.assignment.nil? chain = Solargraph::Parser.chain(pin.assignment, filename) # @sg-ignore flow sensitive typing needs to handle attrs @@ -696,24 +695,19 @@ def declared_externally? pin type = chain.infer(api_map, closure_pin, locals) if type.undefined? && !rules.ignore_all_undefined? base = chain - missing = chain # @type [Solargraph::Pin::Base, nil] found = nil # @type [Array] all_found = [] - closest = ComplexType::UNDEFINED until base.links.first.undefined? all_found = base.define(api_map, closure_pin, locals) found = all_found.first break if found - missing = base base = base.base end all_closest = all_found.map { |pin| pin.typify(api_map) } closest = ComplexType.new(all_closest.flat_map(&:items).uniq) - if !found || closest.defined? || internal?(found) - return false - end + return false if !found || closest.defined? || internal?(found) end true end @@ -736,7 +730,7 @@ def arity_problems_for pin, arguments, location # @param arguments [Array] # @param location [Location] # @return [Array] - def parameterized_arity_problems_for(pin, parameters, arguments, location) + def parameterized_arity_problems_for pin, parameters, arguments, location return [] unless pin.explicit? return [] if parameters.empty? && arguments.empty? return [] if pin.anon_splat? @@ -751,7 +745,7 @@ def parameterized_arity_problems_for(pin, parameters, arguments, location) settled_kwargs = parameters.count(&:keyword?) else kwargs = convert_hash(unchecked.last.node) - if parameters.any? { |param| [:kwarg, :kwoptarg].include?(param.decl) || param.kwrestarg? } + if parameters.any? { |param| %i[kwarg kwoptarg].include?(param.decl) || param.kwrestarg? } if kwargs.empty? add_params += 1 else @@ -780,10 +774,12 @@ def parameterized_arity_problems_for(pin, parameters, arguments, location) return [] if parameters.any?(&:rest?) opt = optional_param_count(parameters) return [] if unchecked.length <= req + opt - if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (parameters.map(&:decl) & [:kwarg, :kwoptarg, :kwrestarg]).any? + if req + add_params + 1 == unchecked.length && any_splatted_call?(unchecked.map(&:node)) && (parameters.map(&:decl) & %i[ + kwarg kwoptarg kwrestarg + ]).any? return [] end - return [] if arguments.length - req == parameters.select { |p| [:optarg, :kwoptarg].include?(p.decl) }.length + return [] if arguments.length - req == parameters.select { |p| %i[optarg kwoptarg].include?(p.decl) }.length return [Problem.new(location, "Too many arguments to #{pin.path}")] elsif unchecked.length < req - settled_kwargs && (arguments.empty? || (!arguments.last.splat? && !arguments.last.links.last.is_a?(Solargraph::Source::Chain::Hash))) # HACK: Kernel#raise signature is incorrect in Ruby 2.7 core docs. @@ -799,35 +795,33 @@ def parameterized_arity_problems_for(pin, parameters, arguments, location) # @todo need to use generic types in method to choose correct # signature and generate Integer as return type # @return [Integer] - def required_param_count(parameters) + def required_param_count parameters parameters.sum { |param| %i[arg kwarg].include?(param.decl) ? 1 : 0 } end # @param parameters [Enumerable] - # @param pin [Pin::Method] + # # @return [Integer] - def optional_param_count(parameters) + def optional_param_count parameters parameters.select { |p| p.decl == :optarg }.length end # @param pin [Pin::Method] - # @sg-ignore need boolish support for ? methods def abstract? pin pin.docstring.has_tag?('abstract') || - # @sg-ignore of low sensitive typing needs to handle ivars - (pin.closure && pin.closure.docstring.has_tag?('abstract')) + pin.closure&.docstring&.has_tag?('abstract') end # @param pin [Pin::Method] # @return [Array] - def fake_args_for(pin) + def fake_args_for pin args = [] with_opts = false with_block = false # @param pin [Pin::Parameter] pin.parameters.each do |pin| # @sg-ignore flow sensitive typing should be able to handle redefinition - if [:kwarg, :kwoptarg, :kwrestarg].include?(pin.decl) + if %i[kwarg kwoptarg kwrestarg].include?(pin.decl) with_opts = true # @sg-ignore flow sensitive typing should be able to handle redefinition elsif pin.decl == :block diff --git a/lib/solargraph/type_checker/rules.rb b/lib/solargraph/type_checker/rules.rb index d551b066d..6ce414a93 100644 --- a/lib/solargraph/type_checker/rules.rb +++ b/lib/solargraph/type_checker/rules.rb @@ -23,11 +23,11 @@ class Rules # @param overrides [Hash{Symbol => Symbol}] def initialize level, overrides @rank = if LEVELS.key?(level) - LEVELS[level] - else - Solargraph.logger.warn "Unrecognized TypeChecker level #{level}, assuming normal" - 0 - end + LEVELS[level] + else + Solargraph.logger.warn "Unrecognized TypeChecker level #{level}, assuming normal" + 0 + end @level = LEVELS[LEVELS.values.index(@rank)] @overrides = overrides end @@ -83,11 +83,12 @@ def require_inferred_type_params? # @todo 13: Need to validate config # @todo 8: flow sensitive typing should support .class == .class # @todo 6: need boolish support for ? methods + # @todo 6: flow sensitive typing needs better handling of ||= on lvars # @todo 5: literal arrays in this module turn into ::Solargraph::Source::Chain::Array # @todo 5: flow sensitive typing needs to handle 'raise if' - # @todo 5: flow sensitive typing needs better handling of ||= on lvars # @todo 4: flow sensitive typing needs to eliminate literal from union with [:bar].include?(foo) # @todo 4: nil? support in flow sensitive typing + # @todo 3: flow sensitive typing ought to be able to handle 'when ClassName' # @todo 2: downcast output of Enumerable#select # @todo 2: flow sensitive typing should handle return nil if location&.name.nil? # @todo 2: flow sensitive typing should handle is_a? and next @@ -98,7 +99,6 @@ def require_inferred_type_params? # @todo 2: Need to handle duck-typed method calls on union types # @todo 2: Need better handling of #compact # @todo 2: flow sensitive typing should allow shadowing of Kernel#caller - # @todo 2: flow sensitive typing ought to be able to handle 'when ClassName' # @todo 1: flow sensitive typing not smart enough to handle this case # @todo 1: flow sensitive typing needs to handle if foo = bar # @todo 1: flow sensitive typing needs to handle "if foo.nil?" @@ -149,7 +149,7 @@ def validate_sg_ignores? # @param type [Symbol] # @param level [Symbol] - def report?(type, level) + def report? type, level rank >= LEVELS[@overrides.fetch(type, level)] end end diff --git a/lib/solargraph/version.rb b/lib/solargraph/version.rb index bfe833039..00cc77b02 100755 --- a/lib/solargraph/version.rb +++ b/lib/solargraph/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Solargraph - VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.59.0.dev.1') + VERSION = ENV.fetch('SOLARGRAPH_FORCE_VERSION', '0.59.0.dev.2') end diff --git a/lib/solargraph/workspace.rb b/lib/solargraph/workspace.rb index f3de99242..351ee28a5 100644 --- a/lib/solargraph/workspace.rb +++ b/lib/solargraph/workspace.rb @@ -80,14 +80,6 @@ def pin_cache @pin_cache ||= fresh_pincache end - # @param stdlib_name [String] - # - # @return [Array] - def stdlib_dependencies stdlib_name - deps = RbsMap::StdlibMap.stdlib_dependencies(stdlib_name, nil) || [] - deps.map { |dep| dep['name'] }.compact - end - # @return [Environ] def global_environ # empty docmap, since the result needs to work in any possible @@ -127,7 +119,7 @@ def yard_plugins # @param level [Symbol] # @return [TypeChecker::Rules] - def rules(level) + def rules level @rules ||= TypeChecker::Rules.new(level, config.type_checker_rules) end @@ -147,7 +139,7 @@ def merge *sources includes_any = false sources.each do |source| # @sg-ignore Need to add nil check here - next unless directory == "*" || config.calculated.include?(source.filename) + next unless directory == '*' || config.calculated.include?(source.filename) # @sg-ignore Need to add nil check here source_hash[source.filename] = source @@ -199,7 +191,7 @@ def source filename def would_require? path require_paths.each do |rp| full = File.join rp, path - return true if File.file?(full) || File.file?(full << ".rb") + return true if File.file?(full) || File.file?(full << '.rb') end false end @@ -223,17 +215,15 @@ def gemspec_files # @return [String, nil] def rbs_collection_path - @gem_rbs_collection ||= read_rbs_collection_path + @rbs_collection_path ||= read_rbs_collection_path end # @return [String, nil] def rbs_collection_config_path @rbs_collection_config_path ||= - begin - unless directory.empty? || directory == '*' - yaml_file = File.join(directory, 'rbs_collection.yaml') - yaml_file if File.file?(yaml_file) - end + unless directory.empty? || directory == '*' + yaml_file = File.join(directory, 'rbs_collection.yaml') + yaml_file if File.file?(yaml_file) end end @@ -251,14 +241,6 @@ def all_gemspecs_from_bundle gemspecs.all_gemspecs_from_bundle end - # @todo make this actually work against bundle instead of pulling - # all installed gemspecs - - # https://github.com/apiology/solargraph/pull/10 - # @return [Array] - def all_gemspecs_from_bundle - Gem::Specification.to_a - end - # @param out [StringIO, IO, nil] output stream for logging # @param rebuild [Boolean] whether to rebuild the pins even if they are cached # @return [void] @@ -278,7 +260,7 @@ def cache_all_for_workspace! out, rebuild: false # which are likely to be newer and have more pins pin_cache.cache_all_stdlibs(out: out, rebuild: rebuild) - out&.puts "Documentation cached for core, standard library and gems." + out&.puts 'Documentation cached for core, standard library and gems.' end # Synchronize the workspace from the provided updater. @@ -323,30 +305,25 @@ def source_hash # @return [void] def load_sources source_hash.clear - unless directory.empty? || directory == '*' - size = config.calculated.length - if config.max_files > 0 and size > config.max_files - raise WorkspaceTooLargeError, - "The workspace is too large to index (#{size} files, #{config.max_files} max)" - end - config.calculated.each do |filename| - begin - source_hash[filename] = Solargraph::Source.load(filename) - rescue Errno::ENOENT => e - Solargraph.logger.warn("Error loading #{filename}: [#{e.class}] #{e.message}") - end - end + return if directory.empty? || directory == '*' + size = config.calculated.length + if config.max_files.positive? && (size > config.max_files) + raise WorkspaceTooLargeError, + "The workspace is too large to index (#{size} files, #{config.max_files} max)" + end + config.calculated.each do |filename| + source_hash[filename] = Solargraph::Source.load(filename) + rescue Errno::ENOENT => e + Solargraph.logger.warn("Error loading #{filename}: [#{e.class}] #{e.message}") end end # @return [void] def require_plugins config.plugins.each do |plugin| - begin - require plugin - rescue LoadError - Solargraph.logger.warn "Failed to load plugin '#{plugin}'" - end + require plugin + rescue LoadError + Solargraph.logger.warn "Failed to load plugin '#{plugin}'" end end diff --git a/lib/solargraph/workspace/config.rb b/lib/solargraph/workspace/config.rb index 31e065e17..bd494b380 100644 --- a/lib/solargraph/workspace/config.rb +++ b/lib/solargraph/workspace/config.rb @@ -54,7 +54,9 @@ def allow? filename # # @return [Array] def calculated - Solargraph.logger.info "Indexing workspace files in #{directory}" unless @calculated || directory.empty? || directory == '*' + unless @calculated || directory.empty? || directory == '*' + Solargraph.logger.info "Indexing workspace files in #{directory}" + end @calculated ||= included - excluded end @@ -146,7 +148,7 @@ def config_data global_config = read_config(global_config_path) defaults = default_config - defaults.merge({'exclude' => []}) unless workspace_config.nil? + defaults.merge({ 'exclude' => [] }) unless workspace_config.nil? defaults .merge(global_config || {}) @@ -160,7 +162,7 @@ def config_data def read_config config_path = '' return nil if config_path.empty? return nil unless File.file?(config_path) - YAML.safe_load(File.read(config_path)) + YAML.safe_load_file(config_path) end # @return [Hash{String => Array, Hash, Integer}] @@ -176,11 +178,11 @@ def default_config 'cops' => 'safe', 'except' => [], 'only' => [], - 'extra_args' =>[] + 'extra_args' => [] } }, 'type_checker' => { - 'rules' => { } + 'rules' => {} }, 'require_paths' => [], 'plugins' => [], @@ -193,12 +195,11 @@ def default_config # @param globs [Array] # @return [Array] def process_globs globs - result = globs.flat_map do |glob| + globs.flat_map do |glob| Dir[File.absolute_path(glob, directory)] - .map{ |f| f.gsub(/\\/, '/') } + .map { |f| f.gsub('\\', '/') } .select { |f| File.file?(f) } end - result end # Modify the included files based on excluded directories and get an @@ -241,7 +242,7 @@ def glob_is_directory? glob # @param glob [String] # @return [String] def glob_to_directory glob - glob.gsub(/(\/\*|\/\*\*\/\*\*?)$/, '') + glob.gsub(%r{(/\*|/\*\*/\*\*?)$}, '') end # @return [Array] diff --git a/lib/solargraph/workspace/gemspecs.rb b/lib/solargraph/workspace/gemspecs.rb index c6af306dd..2389c2d76 100644 --- a/lib/solargraph/workspace/gemspecs.rb +++ b/lib/solargraph/workspace/gemspecs.rb @@ -117,7 +117,7 @@ def find_gem name, version = nil, out: $stderr # # @return [Array] def fetch_dependencies gemspec, out: $stderr - gemspecs = all_gemspecs_from_bundle + all_gemspecs_from_bundle # @type [Hash{String => Gem::Specification}] deps_so_far = {} diff --git a/lib/solargraph/yard_map/helpers.rb b/lib/solargraph/yard_map/helpers.rb index cd4be9acc..8c1747d9a 100644 --- a/lib/solargraph/yard_map/helpers.rb +++ b/lib/solargraph/yard_map/helpers.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Solargraph class YardMap module Helpers @@ -22,10 +24,12 @@ def object_location code_object, spec # @param code_object [YARD::CodeObjects::Base] # @param spec [Gem::Specification, nil] # @return [Solargraph::Pin::Namespace] - def create_closure_namespace_for(code_object, spec) + def create_closure_namespace_for code_object, spec code_object_for_location = code_object # code_object.namespace is sometimes a YARD proxy object pointing to a method path ("Object#new") - code_object_for_location = code_object.namespace if code_object.namespace.is_a?(YARD::CodeObjects::NamespaceObject) + if code_object.namespace.is_a?(YARD::CodeObjects::NamespaceObject) + code_object_for_location = code_object.namespace + end namespace_location = object_location(code_object_for_location, spec) ns_name = code_object.namespace.to_s if ns_name.empty? diff --git a/lib/solargraph/yard_map/mapper.rb b/lib/solargraph/yard_map/mapper.rb index f0708e9d9..a0109189b 100644 --- a/lib/solargraph/yard_map/mapper.rb +++ b/lib/solargraph/yard_map/mapper.rb @@ -35,28 +35,27 @@ def map # @return [Array] def generate_pins code_object result = [] - if code_object.is_a?(YARD::CodeObjects::NamespaceObject) + case code_object + when YARD::CodeObjects::NamespaceObject nspin = ToNamespace.make(code_object, @spec, @namespace_pins[code_object.namespace.to_s]) @namespace_pins[code_object.path] = nspin result.push nspin - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - if code_object.is_a?(YARD::CodeObjects::ClassObject) and !code_object.superclass.nil? + if code_object.is_a?(YARD::CodeObjects::ClassObject) && !code_object.superclass.nil? # This method of superclass detection is a bit of a hack. If # the superclass is a Proxy, it is assumed to be undefined in its # yardoc and converted to a fully qualified namespace. - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check superclass = if code_object.superclass.is_a?(YARD::CodeObjects::Proxy) - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - "::#{code_object.superclass}" - else - # @sg-ignore flow sensitive typing needs to narrow down type with an if is_a? check - code_object.superclass.to_s - end + "::#{code_object.superclass}" + else + code_object.superclass.to_s + end result.push Solargraph::Pin::Reference::Superclass.new(name: superclass, closure: nspin, source: :yard_map) end + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' code_object.class_mixins.each do |m| result.push Solargraph::Pin::Reference::Extend.new(closure: nspin, name: m.path, source: :yard_map) end + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' code_object.instance_mixins.each do |m| result.push Solargraph::Pin::Reference::Include.new( closure: nspin, # @todo Fix this @@ -64,8 +63,9 @@ def generate_pins code_object source: :yard_map ) end - elsif code_object.is_a?(YARD::CodeObjects::MethodObject) + when YARD::CodeObjects::MethodObject closure = @namespace_pins[code_object.namespace.to_s] + # @sg-ignore flow sensitive typing ought to be able to handle 'when ClassName' if code_object.name == :initialize && code_object.scope == :instance # @todo Check the visibility of .new result.push ToMethod.make(code_object, 'new', :class, :public, closure, @spec) @@ -73,7 +73,7 @@ def generate_pins code_object else result.push ToMethod.make(code_object, nil, nil, nil, closure, @spec) end - elsif code_object.is_a?(YARD::CodeObjects::ConstantObject) + when YARD::CodeObjects::ConstantObject closure = @namespace_pins[code_object.namespace] result.push ToConstant.make(code_object, closure, @spec) end diff --git a/lib/solargraph/yard_map/mapper/to_method.rb b/lib/solargraph/yard_map/mapper/to_method.rb index 42593ed8c..047f53957 100644 --- a/lib/solargraph/yard_map/mapper/to_method.rb +++ b/lib/solargraph/yard_map/mapper/to_method.rb @@ -9,8 +9,8 @@ module ToMethod # @type [Hash{Array => Symbol}] VISIBILITY_OVERRIDE = { # YARD pays attention to 'private' statements prior to class methods but shouldn't - ["Rails::Engine", :class, "find_root_with_flag"] => :public - } + ['Rails::Engine', :class, 'find_root_with_flag'] => :public + }.freeze # @param code_object [YARD::CodeObjects::MethodObject] # @param name [String, nil] @@ -32,7 +32,9 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = # @sg-ignore Need to add nil check here final_visibility ||= VISIBILITY_OVERRIDE[[closure.path, final_scope]] # @sg-ignore Need to add nil check here - final_visibility ||= :private if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name.to_sym) + if closure.path == 'Kernel' && Kernel.private_instance_methods(false).include?(name.to_sym) + final_visibility ||= :private + end final_visibility ||= visibility final_visibility ||= :private if code_object.module_function? && final_scope == :instance final_visibility ||= :public if code_object.module_function? && final_scope == :class @@ -50,7 +52,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = explicit: code_object.is_explicit?, return_type: return_type, parameters: [], - source: :yardoc, + source: :yardoc ) else # @sg-ignore Need to add nil check here @@ -66,7 +68,7 @@ def self.make code_object, name = nil, scope = nil, visibility = nil, closure = return_type: return_type, attribute: code_object.is_attribute?, parameters: [], - source: :yardoc, + source: :yardoc ) pin.parameters.concat get_parameters(code_object, location, comments, pin) pin.parameters.freeze @@ -99,7 +101,7 @@ def get_parameters code_object, location, comments, pin presence: nil, decl: arg_type(a), asgn_code: a[1], - source: :yardoc, + source: :yardoc ) end end diff --git a/lib/solargraph/yard_map/mapper/to_namespace.rb b/lib/solargraph/yard_map/mapper/to_namespace.rb index 7d1d3ce6e..3e0887ecd 100644 --- a/lib/solargraph/yard_map/mapper/to_namespace.rb +++ b/lib/solargraph/yard_map/mapper/to_namespace.rb @@ -23,7 +23,7 @@ def self.make code_object, spec, closure = nil closure: closure, # @sg-ignore need to add a nil check here gates: closure.gates, - source: :yardoc, + source: :yardoc ) end end diff --git a/lib/solargraph/yard_tags.rb b/lib/solargraph/yard_tags.rb index c34710b63..462138f9c 100644 --- a/lib/solargraph/yard_tags.rb +++ b/lib/solargraph/yard_tags.rb @@ -14,7 +14,7 @@ def call; end end # Define a @type tag for documenting variables -YARD::Tags::Library.define_tag("Type", :type, :with_types_and_name) +YARD::Tags::Library.define_tag('Type', :type, :with_types_and_name) # Define an @!override directive for overriding method tags -YARD::Tags::Library.define_directive("override", :with_name, Solargraph::DomainDirective) +YARD::Tags::Library.define_directive('override', :with_name, Solargraph::DomainDirective) diff --git a/solargraph.gemspec b/solargraph.gemspec index a3e8e88cd..06edbf19f 100755 --- a/solargraph.gemspec +++ b/solargraph.gemspec @@ -1,76 +1,78 @@ -# @sg-ignore Should better support meaning of '&' in RBS -$LOAD_PATH.unshift File.dirname(__FILE__) + '/lib' -require 'solargraph/version' -require 'date' - -# @param s [Gem::Specification] -Gem::Specification.new do |s| - s.name = 'solargraph' - s.version = Solargraph::VERSION - s.date = Date.today.strftime("%Y-%m-%d") - s.summary = "A Ruby language server" - s.description = "IDE tools for code completion, inline documentation, and static analysis" - s.authors = ["Fred Snyder"] - s.email = 'admin@castwide.com' - s.files = Dir.chdir(File.expand_path('..', __FILE__)) do - # @sg-ignore Need backtick support - # @type [String] - all_files = `git ls-files -z` - all_files.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end - s.homepage = 'https://solargraph.org' - s.license = 'MIT' - s.executables = ['solargraph'] - s.metadata["funding_uri"] = "https://www.patreon.com/castwide" - s.metadata["bug_tracker_uri"] = "https://github.com/castwide/solargraph/issues" - s.metadata["changelog_uri"] = "https://github.com/castwide/solargraph/blob/master/CHANGELOG.md" - s.metadata["source_code_uri"] = "https://github.com/castwide/solargraph" - s.metadata["rubygems_mfa_required"] = "true" - - s.required_ruby_version = '>= 3.0' - - s.add_runtime_dependency 'ast', '~> 2.4.3' - s.add_runtime_dependency 'backport', '~> 1.2' - s.add_runtime_dependency 'benchmark', '~> 0.4' - s.add_runtime_dependency 'bundler', '>= 2.0' - s.add_runtime_dependency 'diff-lcs', '~> 1.4' - s.add_runtime_dependency 'jaro_winkler', '~> 1.6', '>= 1.6.1' - s.add_runtime_dependency 'kramdown', '~> 2.3' - s.add_runtime_dependency 'kramdown-parser-gfm', '~> 1.1' - s.add_runtime_dependency 'logger', '~> 1.6' - s.add_runtime_dependency 'observer', '~> 0.1' - s.add_runtime_dependency 'ostruct', '~> 0.6' - s.add_runtime_dependency 'open3', '~> 0.2.1' - s.add_runtime_dependency 'parser', '~> 3.0' - s.add_runtime_dependency 'prism', '~> 1.4' - s.add_runtime_dependency 'rbs', ['>= 3.6.1', '<= 4.0.0.dev.5'] - s.add_runtime_dependency 'reverse_markdown', '~> 3.0' - s.add_runtime_dependency 'rubocop', '~> 1.76' - s.add_runtime_dependency 'thor', '~> 1.0' - s.add_runtime_dependency 'tilt', '~> 2.0' - s.add_runtime_dependency 'yard', '~> 0.9', '>= 0.9.24' - s.add_runtime_dependency 'yard-solargraph', '~> 0.1' - s.add_runtime_dependency 'yard-activesupport-concern', '~> 0.0' - - s.add_development_dependency 'pry', '~> 0.15' - s.add_development_dependency 'public_suffix', '~> 3.1' - s.add_development_dependency 'rake', '~> 13.2' - s.add_development_dependency 'rspec', '~> 3.5' - # - # very specific development-time RuboCop version patterns for CI - # stability - feel free to update in an isolated PR - # - # even more specific on RuboCop itself, which is written into _todo - # file. - s.add_development_dependency 'rubocop', '~> 1.80.0.0' - s.add_development_dependency 'rubocop-rake', '~> 0.7.1' - s.add_development_dependency 'rubocop-rspec', '~> 3.6.0' - s.add_development_dependency 'rubocop-yard', '~> 1.0.0' - s.add_development_dependency 'simplecov', '~> 0.21' - s.add_development_dependency 'simplecov-lcov', '~> 0.8' - s.add_development_dependency 'undercover', '~> 0.7' - s.add_development_dependency 'overcommit', '~> 0.68.0' - s.add_development_dependency 'webmock', '~> 3.6' - # work around missing yard dependency needed as of Ruby 3.5 - s.add_development_dependency 'irb', '~> 1.15' -end +# frozen_string_literal: true + +# @sg-ignore Should better support meaning of '&' in RBS +$LOAD_PATH.unshift "#{File.dirname(__FILE__)}/lib" +require 'solargraph/version' +require 'date' + +# @param s [Gem::Specification] +Gem::Specification.new do |s| + s.name = 'solargraph' + s.version = Solargraph::VERSION + s.summary = 'A Ruby language server' + s.description = 'IDE tools for code completion, inline documentation, and static analysis' + s.authors = ['Fred Snyder'] + s.email = 'admin@castwide.com' + s.files = Dir.chdir(File.expand_path(__dir__)) do + # @sg-ignore Need backtick support + # @type [String] + all_files = `git ls-files -z` + all_files.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + end + s.homepage = 'https://solargraph.org' + s.license = 'MIT' + s.executables = ['solargraph'] + s.metadata['funding_uri'] = 'https://www.patreon.com/castwide' + s.metadata['bug_tracker_uri'] = 'https://github.com/castwide/solargraph/issues' + s.metadata['changelog_uri'] = 'https://github.com/castwide/solargraph/blob/master/CHANGELOG.md' + s.metadata['source_code_uri'] = 'https://github.com/castwide/solargraph' + s.metadata['rubygems_mfa_required'] = 'true' + + s.required_ruby_version = '>= 3.0' + + s.add_dependency 'ast', '~> 2.4.3' + s.add_dependency 'backport', '~> 1.2' + s.add_dependency 'benchmark', '~> 0.4' + s.add_dependency 'bundler', '>= 2.0' + s.add_dependency 'diff-lcs', '~> 1.4' + s.add_dependency 'jaro_winkler', '~> 1.6', '>= 1.6.1' + s.add_dependency 'kramdown', '~> 2.3' + s.add_dependency 'kramdown-parser-gfm', '~> 1.1' + s.add_dependency 'logger', '~> 1.6' + s.add_dependency 'observer', '~> 0.1' + s.add_dependency 'open3', '~> 0.2.1' + s.add_dependency 'ostruct', '~> 0.6' + s.add_dependency 'parser', '~> 3.0' + s.add_dependency 'prism', '~> 1.4' + s.add_dependency 'rbs', ['>= 3.6.1', '<= 4.0.0.dev.5'] + s.add_dependency 'reverse_markdown', '~> 3.0' + s.add_dependency 'rubocop', '~> 1.76' + s.add_dependency 'thor', '~> 1.0' + s.add_dependency 'tilt', '~> 2.0' + s.add_dependency 'yard', '~> 0.9', '>= 0.9.24' + s.add_dependency 'yard-activesupport-concern', '~> 0.0' + s.add_dependency 'yard-solargraph', '~> 0.1' + + s.add_development_dependency 'pry', '~> 0.15' + s.add_development_dependency 'public_suffix', '~> 3.1' + s.add_development_dependency 'rake', '~> 13.2' + s.add_development_dependency 'rspec', '~> 3.5' + # + # very specific development-time RuboCop version patterns for CI + # stability - feel free to update in an isolated PR + # + # even more specific on RuboCop itself, which is written into _todo + # file. + s.add_development_dependency 'overcommit', '~> 0.68.0' + s.add_development_dependency 'rubocop', '~> 1.80.0.0' + s.add_development_dependency 'rubocop-rake', '~> 0.7.1' + s.add_development_dependency 'rubocop-rspec', '~> 3.6.0' + s.add_development_dependency 'rubocop-yard', '~> 1.0.0' + s.add_development_dependency 'simplecov', '~> 0.21' + s.add_development_dependency 'simplecov-lcov', '~> 0.8' + s.add_development_dependency 'undercover', '~> 0.7' + s.add_development_dependency 'vernier', '< 2' + s.add_development_dependency 'webmock', '~> 3.6' + # work around missing yard dependency needed as of Ruby 3.5 + s.add_development_dependency 'irb', '~> 1.15' +end diff --git a/spec/api_map/cache_spec.rb b/spec/api_map/cache_spec.rb index 20e5b9df1..392f04fe4 100644 --- a/spec/api_map/cache_spec.rb +++ b/spec/api_map/cache_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::ApiMap::Cache do - it "recognizes empty caches" do - cache = Solargraph::ApiMap::Cache.new + it 'recognizes empty caches' do + cache = described_class.new expect(cache).to be_empty cache.set_methods('', :class, [:public], true, []) expect(cache).not_to be_empty diff --git a/spec/api_map/config_spec.rb b/spec/api_map/config_spec.rb index 5790265cd..993cb9a97 100644 --- a/spec/api_map/config_spec.rb +++ b/spec/api_map/config_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'tmpdir' describe Solargraph::Workspace::Config do @@ -5,62 +7,62 @@ let(:workspace_path) { File.realpath(Dir.mktmpdir) } let(:global_path) { @global_path } - before(:each) do + before do @global_path = File.realpath(Dir.mktmpdir) - @orig_env = ENV['SOLARGRAPH_GLOBAL_CONFIG'] + @orig_env = ENV.fetch('SOLARGRAPH_GLOBAL_CONFIG', nil) ENV['SOLARGRAPH_GLOBAL_CONFIG'] = File.join(@global_path, '.solargraph.yml') end - after(:each) do + after do ENV['SOLARGRAPH_GLOBAL_CONFIG'] = @orig_env FileUtils.remove_entry(workspace_path) FileUtils.remove_entry(global_path) end - it "reads workspace files from config" do + it 'reads workspace files from config' do File.write(File.join(workspace_path, 'foo.rb'), 'test') File.write(File.join(workspace_path, 'bar.rb'), 'test') File.open(File.join(workspace_path, '.solargraph.yml'), 'w') do |file| - file.puts "include:" - file.puts " - foo.rb" - file.puts "exclude:" - file.puts " - bar.rb" + file.puts 'include:' + file.puts ' - foo.rb' + file.puts 'exclude:' + file.puts ' - bar.rb' end expect(config.included).to eq([File.join(workspace_path, 'foo.rb')]) expect(config.excluded).to eq([File.join(workspace_path, 'bar.rb')]) end - it "reads workspace files from global config" do + it 'reads workspace files from global config' do File.write(File.join(workspace_path, 'foo.rb'), 'test') File.write(File.join(workspace_path, 'bar.rb'), 'test') File.open(File.join(global_path, '.solargraph.yml'), 'w') do |file| - file.puts "include:" - file.puts " - foo.rb" - file.puts "exclude:" - file.puts " - bar.rb" + file.puts 'include:' + file.puts ' - foo.rb' + file.puts 'exclude:' + file.puts ' - bar.rb' end expect(config.included).to eq([File.join(workspace_path, 'foo.rb')]) expect(config.excluded).to eq([File.join(workspace_path, 'bar.rb')]) end - it "overrides global config with workspace config" do + it 'overrides global config with workspace config' do File.write(File.join(workspace_path, 'foo.rb'), 'test') File.write(File.join(workspace_path, 'bar.rb'), 'test') - + File.open(File.join(workspace_path, '.solargraph.yml'), 'w') do |file| - file.puts "include:" - file.puts " - foo.rb" - file.puts "max_files: 8000" + file.puts 'include:' + file.puts ' - foo.rb' + file.puts 'max_files: 8000' end File.open(File.join(global_path, '.solargraph.yml'), 'w') do |file| - file.puts "include:" - file.puts " - include.rb" - file.puts "exclude:" - file.puts " - bar.rb" - file.puts "max_files: 1000" + file.puts 'include:' + file.puts ' - include.rb' + file.puts 'exclude:' + file.puts ' - bar.rb' + file.puts 'max_files: 1000' end expect(config.included).to eq([File.join(workspace_path, 'foo.rb')]) diff --git a/spec/api_map/source_to_yard_spec.rb b/spec/api_map/source_to_yard_spec.rb index 88be54c64..c232ca41d 100644 --- a/spec/api_map/source_to_yard_spec.rb +++ b/spec/api_map/source_to_yard_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::ApiMap::SourceToYard do - it "rakes sources" do + it 'rakes sources' do source = Solargraph::SourceMap.load_string(%( module Foo class Bar @@ -9,7 +11,7 @@ def baz end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) expect(object.code_object_paths.length).to eq(3) expect(object.code_object_paths).to include('Foo') @@ -17,7 +19,7 @@ def baz expect(object.code_object_paths).to include('Foo::Bar#baz') end - it "generates docstrings" do + it 'generates docstrings' do source = Solargraph::SourceMap.load_string(%( # My foo class 描述 class Foo @@ -30,7 +32,7 @@ def self.baz end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) class_object = object.code_object_at('Foo') expect(class_object.docstring).to eq('My foo class 描述') @@ -40,7 +42,7 @@ def self.baz expect(class_method_object.tag(:return).types).to eq(['Foo']) end - it "generates instance mixins" do + it 'generates instance mixins' do source = Solargraph::SourceMap.load_string(%( module Foo def bar @@ -51,14 +53,14 @@ class Baz end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) module_object = object.code_object_at('Foo') class_object = object.code_object_at('Baz') expect(class_object.instance_mixins).to include(module_object) end - it "generates class mixins" do + it 'generates class mixins' do source = Solargraph::SourceMap.load_string(%( module Foo def bar; end @@ -68,14 +70,14 @@ class Baz end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) module_object = object.code_object_at('Foo') class_object = object.code_object_at('Baz') expect(class_object.class_mixins).to include(module_object) end - it "generates methods for attributes" do + it 'generates methods for attributes' do source = Solargraph::SourceMap.load_string(%( class Foo attr_reader :bar @@ -84,17 +86,17 @@ class Foo end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) - expect(object.code_object_at('Foo#bar')).not_to be(nil) - expect(object.code_object_at('Foo#bar=')).to be(nil) - expect(object.code_object_at('Foo#baz')).to be(nil) - expect(object.code_object_at('Foo#baz=')).not_to be(nil) - expect(object.code_object_at('Foo#boo')).not_to be(nil) - expect(object.code_object_at('Foo#boo=')).not_to be(nil) + expect(object.code_object_at('Foo#bar')).not_to be_nil + expect(object.code_object_at('Foo#bar=')).to be_nil + expect(object.code_object_at('Foo#baz')).to be_nil + expect(object.code_object_at('Foo#baz=')).not_to be_nil + expect(object.code_object_at('Foo#boo')).not_to be_nil + expect(object.code_object_at('Foo#boo=')).not_to be_nil end - it "generates method parameters" do + it 'generates method parameters' do source = Solargraph::SourceMap.load_string(%( class Foo def bar baz, boo = 'boo' @@ -102,7 +104,7 @@ def bar baz, boo = 'boo' end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) method_object = object.code_object_at('Foo#bar') expect(method_object.parameters.length).to eq(2) @@ -110,7 +112,7 @@ def bar baz, boo = 'boo' expect(method_object.parameters[1]).to eq(['boo', "'boo'"]) end - it "generates method keyword parameters" do + it 'generates method keyword parameters' do source = Solargraph::SourceMap.load_string(%( class Foo def bar baz, boo: 'boo' @@ -118,7 +120,7 @@ def bar baz, boo: 'boo' end )) object = Object.new - object.extend Solargraph::ApiMap::SourceToYard + object.extend described_class object.rake_yard Solargraph::ApiMap::Store.new(source.pins) method_object = object.code_object_at('Foo#bar') expect(method_object.parameters.length).to eq(2) diff --git a/spec/api_map/store_spec.rb b/spec/api_map/store_spec.rb index dc8413de5..059c3eb1b 100644 --- a/spec/api_map/store_spec.rb +++ b/spec/api_map/store_spec.rb @@ -4,7 +4,7 @@ it 'indexes multiple pinsets' do foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar') - store = Solargraph::ApiMap::Store.new([foo_pin], [bar_pin]) + store = described_class.new([foo_pin], [bar_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) expect(store.get_path_pins('Bar')).to eq([bar_pin]) @@ -13,7 +13,7 @@ it 'indexes empty pinsets' do foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') - store = Solargraph::ApiMap::Store.new([], [foo_pin]) + store = described_class.new([], [foo_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) end @@ -21,7 +21,7 @@ foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar') baz_pin = Solargraph::Pin::Namespace.new(name: 'Baz') - store = Solargraph::ApiMap::Store.new([foo_pin], [bar_pin]) + store = described_class.new([foo_pin], [bar_pin]) store.update([foo_pin], [baz_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) @@ -32,7 +32,7 @@ it 'updates new pinsets' do foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar') - store = Solargraph::ApiMap::Store.new([foo_pin]) + store = described_class.new([foo_pin]) store.update([foo_pin], [bar_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) @@ -42,7 +42,7 @@ it 'updates empty stores' do foo_pin = Solargraph::Pin::Namespace.new(name: 'Foo') bar_pin = Solargraph::Pin::Namespace.new(name: 'Bar') - store = Solargraph::ApiMap::Store.new + store = described_class.new store.update([foo_pin, bar_pin]) expect(store.get_path_pins('Foo')).to eq([foo_pin]) @@ -56,20 +56,20 @@ class Foo; end class Bar < Foo; end ), 'test.rb') - store = Solargraph::ApiMap::Store.new(map.pins) + store = described_class.new(map.pins) ref = store.get_superclass('Bar') expect(ref.name).to eq('Foo') end it 'returns Boolean superclass' do - store = Solargraph::ApiMap::Store.new + store = described_class.new ref = store.get_superclass('TrueClass') expect(ref.name).to eq('Boolean') end it 'maps core Errno classes' do map = Solargraph::RbsMap::CoreMap.new - store = Solargraph::ApiMap::Store.new(map.pins) + store = described_class.new(map.pins) Errno.constants.each do |const| pin = store.get_path_pins("Errno::#{const}").first expect(pin).to be_a(Solargraph::Pin::Namespace) diff --git a/spec/api_map_method_spec.rb b/spec/api_map_method_spec.rb index a75428d22..87469562b 100644 --- a/spec/api_map_method_spec.rb +++ b/spec/api_map_method_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -describe 'Solargraph::ApiMap methods' do - let(:api_map) { Solargraph::ApiMap.new } +describe Solargraph::ApiMap do + let(:api_map) { described_class.new } let(:bench) do Solargraph::Bench.new(external_requires: external_requires, workspace: Solargraph::Workspace.new('.')) end @@ -37,7 +37,7 @@ module Bar Bar::Baz ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [11, 8]) expect(clip.infer.to_s).to eq('Symbol') @@ -60,7 +60,7 @@ module Bar a ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [13, 8]) expect(clip.infer.to_s).to eq('Symbol') @@ -85,7 +85,7 @@ module Bar a ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [15, 8]) expect(clip.infer.to_s).to eq('Symbol') @@ -110,7 +110,7 @@ class B a ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [15, 8]) expect(clip.infer.to_s).to eq('Symbol') @@ -119,7 +119,7 @@ class B describe '#get_method_stack' do let(:out) { StringIO.new } - let(:api_map) { Solargraph::ApiMap.load_with_cache(Dir.pwd, out) } + let(:api_map) { described_class.load_with_cache(Dir.pwd, out) } context 'with stdlib that has vital dependencies' do let(:external_requires) { ['yaml'] } @@ -142,7 +142,7 @@ class B describe '#cache_all_for_doc_map!' do it 'can cache gems without a bench' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new doc_map = instance_double(Solargraph::DocMap, cache_doc_map_gems!: true) allow(Solargraph::DocMap).to receive(:new).and_return(doc_map) api_map.cache_all_for_doc_map!(out: $stderr) @@ -152,14 +152,14 @@ class B describe '#workspace' do it 'can get a default workspace without a bench' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new expect(api_map.workspace).not_to be_nil end end describe '#uncached_gemspecs' do it 'can get uncached gemspecs workspace without a bench' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new expect(api_map.uncached_gemspecs).not_to be_nil end end @@ -178,7 +178,7 @@ class Includer end ), 'test.rb') - api_map = Solargraph::ApiMap.new + api_map = described_class.new api_map.map source pins = api_map.get_methods('Foo::Includer') expect(pins.map(&:path)).to include('Foo::Bar#baz') diff --git a/spec/api_map_spec.rb b/spec/api_map_spec.rb index f76adb078..facf9489c 100755 --- a/spec/api_map_spec.rb +++ b/spec/api_map_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + require 'tmpdir' describe Solargraph::ApiMap do before :all do - @api_map = Solargraph::ApiMap.new + @api_map = described_class.new end it 'returns core methods' do @@ -114,8 +116,9 @@ class Baz expect(paths).to include('Foo::Baz') end - # @todo Working on context resolution - xit 'finds nested namespaces within a context' do + it 'finds nested namespaces within a context' do + pending('better context resolution') + map = Solargraph::SourceMap.load_string(%( module Foo class Bar @@ -130,8 +133,9 @@ class Baz expect(pins.map(&:path)).to include('Foo::Bar::BAR_CONSTANT') end - # @todo This might be invalid now - xit 'checks constant visibility' do + it 'checks constant visibility' do + pending('This might be invalid now') + map = Solargraph::SourceMap.load_string(%( module Foo FOO_CONSTANT = 'foo' @@ -158,13 +162,6 @@ module Foo expect(pins.map(&:path)).to include('String#upcase') end - it 'gets class methods for complex types' do - @api_map.index [] - type = Solargraph::ComplexType.parse('Class') - pins = @api_map.get_complex_type_methods(type) - expect(pins.map(&:path)).to include('String.try_convert') - end - it 'checks visibility of complex type methods' do map = Solargraph::SourceMap.load_string(%( class Foo @@ -195,7 +192,7 @@ def prot end it 'adds Object instance methods to duck types' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new type = Solargraph::ComplexType.parse('#foo') pins = api_map.get_complex_type_methods(type) expect(pins.any? { |p| p.namespace == 'BasicObject' }).to be(true) @@ -437,14 +434,14 @@ class Sup method_pins = @api_map.get_method_stack("Array(1, 2, 'a')", 'include?') method_pin = method_pins.first - expect(method_pin).to_not be_nil + expect(method_pin).not_to be_nil expect(method_pin.path).to eq('Array#include?') parameter_type = method_pin.signatures.first.parameters.first.return_type expect(parameter_type.rooted_tags).to eq("1, 2, 'a'") end it 'loads workspaces from directories' do - api_map = Solargraph::ApiMap.load('spec/fixtures/workspace') + api_map = described_class.load('spec/fixtures/workspace') expect(api_map.source_map(File.absolute_path('spec/fixtures/workspace/app.rb'))).to be_a(Solargraph::SourceMap) end @@ -462,8 +459,7 @@ class Container expect(pins.map(&:path)).to include('Mixin::FOO') end - # @todo This test needs changed - xit 'sorts constants by name' do + it 'sorts constants by name' do source = Solargraph::Source.load_string(%( module Foo AAB = 'aaa' @@ -536,15 +532,11 @@ module Includer end # @todo Qualify methods might not accept parametrized types anymore - xit 'handles multiple type parameters without losing cache coherence' do + it 'handles multiple type parameters without losing cache coherence' do tag = @api_map.qualify('Array') expect(tag).to eq('Array') tag = @api_map.qualify('Array') expect(tag).to eq('Array') - end - - # @todo Qualify methods might not accept parametrized types anymore - xit 'handles multiple type parameters without losing cache coherence' do tag = @api_map.qualify('Hash{Integer => String}') expect(tag).to eq('Hash{Integer => String}') end @@ -679,8 +671,9 @@ class Container expect(paths).to eq(['Prepended::PRE_CONST']) end - # @todo This test fails with lazy dynamic rebinding - xit 'finds instance variables in yieldreceiver blocks' do + it 'finds instance variables in yieldreceiver blocks' do + pending('lazy dynamic rebinding fixes') + source = Solargraph::Source.load_string(%( module Container # @yieldreceiver [Container] @@ -761,17 +754,17 @@ def bar; end end it 'can qualify "Boolean"' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new expect(api_map.qualify('Boolean')).to eq('Boolean') end it 'knows that true is a "subtype" of Boolean' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new expect(api_map.super_and_sub?('Boolean', 'true')).to be(true) end it 'knows that false is a "subtype" of Boolean' do - api_map = Solargraph::ApiMap.new + api_map = described_class.new expect(api_map.super_and_sub?('Boolean', 'false')).to be(true) end @@ -799,7 +792,7 @@ class Foo mixin = Solargraph::Pin::Reference::Include.new( name: 'defined?(DidYouMean::SpellChecker) && defined?(DidYouMean::Correctable)', closure: closure ) - api_map = Solargraph::ApiMap.new(pins: [closure, mixin]) + api_map = described_class.new(pins: [closure, mixin]) expect(api_map.get_method_stack('Foo', 'foo')).to be_empty end @@ -820,7 +813,7 @@ def baz end ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) + api_map = described_class.new.map(source) clip = api_map.clip_at('test.rb', [11, 10]) expect(clip.infer.to_s).to eq('Symbol') diff --git a/spec/complex_type_spec.rb b/spec/complex_type_spec.rb index 8a5b273dd..3075aaaaf 100644 --- a/spec/complex_type_spec.rb +++ b/spec/complex_type_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe 'YARD type specifier list parsing' do - context 'in compliance with https://www.rubydoc.info/gems/yard/file/docs/Tags.md#type-list-conventions' do + context 'with https://www.rubydoc.info/gems/yard/file/docs/Tags.md#type-list-conventions compliance' do # Types Specifier List # # In some cases, a tag will allow for a "types specifier list"; this @@ -121,7 +123,7 @@ # types. This type does not exist in Ruby, however. it 'typifies Booleans' do - api_map = double(Solargraph::ApiMap, qualify: nil) + api_map = instance_double(Solargraph::ApiMap, qualify: nil) type = Solargraph::ComplexType.parse('::Boolean') qualified = type.qualify(api_map) expect(qualified.tag).to eq('Boolean') @@ -160,7 +162,6 @@ expect(types.to_rbs).to eq('Array[Symbol, String]') end - # Note that parametrized types are typically not order-dependent, in # other words, a list of parametrized types can occur in any order # inside of a type. An array specified as Array can @@ -216,7 +217,7 @@ types = Solargraph::ComplexType.parse('Hash{String, Symbol => Integer, BigDecimal}') expect(types.length).to eq(1) type = types.first - expect(type.hash_parameters?).to eq(true) + expect(type.hash_parameters?).to be(true) expect(type.key_types.map(&:name)).to eq(%w[String Symbol]) expect(type.value_types.map(&:name)).to eq(%w[Integer BigDecimal]) expect(type.to_rbs).to eq('Hash[(String | Symbol), (Integer | BigDecimal)]') @@ -340,7 +341,7 @@ xit 'understands reference tags' end - context 'offers machine users error messages given non-sensical types' do + context 'when given non-sensical types by machine users' do it 'raises ComplexTypeError for unmatched brackets' do expect do Solargraph::ComplexType.parse('Array and Module<> from type' do + context 'when offering type queries orthogonal to YARD spec' do + context 'when defining namespace concept which strips Class<> and Module<> from type' do # # Solargraph extensions and library features # @@ -409,7 +410,7 @@ end end - context 'simplifies type representation on output' do + context 'when simplifying type representation on output' do it 'throws away other types when in union with an undefined' do type = Solargraph::ComplexType.parse('Symbol, String, Array(Integer, Integer), undefined') expect(type.to_s).to eq('undefined') @@ -447,7 +448,7 @@ end end - context 'defines rooted and unrooted concept' do + context 'when defining rooted and unrooted concept' do it 'identify rooted types' do types = Solargraph::ComplexType.parse '::Array' expect(types.map(&:rooted?)).to eq([true]) @@ -467,7 +468,7 @@ end end - context 'allows users to define their own generic types' do + context 'when allowing users to define their own generic types' do it 'recognizes param types' do type = Solargraph::ComplexType.parse('generic') expect(type).to be_generic @@ -536,20 +537,20 @@ ['generic', 'Array>', { 'B' => 'Integer' }, 'Array', { 'B' => 'Integer', 'A' => 'Array' }], ['Array>', 'Array', {}, 'Array', { 'A' => 'String' }] - ] + ].freeze UNIQUE_METHOD_GENERIC_TESTS.each do |tag, context_type_tag, unfrozen_input_map, expected_tag, expected_output_map| - context "resolves #{tag} with context #{context_type_tag} and existing resolved generics #{unfrozen_input_map}" do + context "when resolveing #{tag} with context #{context_type_tag} and existing resolved generics #{unfrozen_input_map}" do let(:complex_type) { Solargraph::ComplexType.parse(tag) } let(:unique_type) { complex_type.first } - it '#{tag} is a unique type' do + let(:context_type) { Solargraph::ComplexType.parse(context_type_tag) } + let(:generic_value) { unfrozen_input_map.transform_values! { |tag| Solargraph::ComplexType.parse(tag) } } + + it "#{tag} is a unique type" do expect(complex_type.length).to eq(1) end - let(:generic_value) { unfrozen_input_map.transform_values! { |tag| Solargraph::ComplexType.parse(tag) } } - let(:context_type) { Solargraph::ComplexType.parse(context_type_tag) } - it "resolves to #{expected_tag} with updated map #{expected_output_map}" do resolved_generic_values = unfrozen_input_map.transform_values { |tag| Solargraph::ComplexType.parse(tag) } resolved_type = unique_type.resolve_generics_from_context(expected_output_map.keys, context_type, @@ -562,7 +563,7 @@ end end - context 'identifies type of parameter syntax used' do + context 'when identifying type of parameter syntax used' do it 'raises NoMethodError for missing methods' do type = Solargraph::ComplexType.parse('String') expect { type.undefined_method }.to raise_error(NoMethodError) @@ -589,9 +590,9 @@ end end - context "'qualifies' types by resolving relative references to types to absolute references (fully qualified types)" do + context "when 'qualifying' types by resolving relative references to types to absolute references (fully qualified types)" do it 'returns undefined for unqualified types' do - api_map = double(Solargraph::ApiMap, qualify: nil) + api_map = instance_double(Solargraph::ApiMap, qualify: nil) type = Solargraph::ComplexType.parse('UndefinedClass') qualified = type.qualify(api_map) expect(qualified).to be_undefined @@ -599,7 +600,7 @@ end end - context 'allows list-of-types to be destructively cast down to a single type' do + context 'when allowing list-of-types to be destructively cast down to a single type' do it 'returns the first type when multiple were parsed with #tag' do type = Solargraph::ComplexType.parse('String, Array') expect(type.tag).to eq('String') @@ -607,7 +608,22 @@ end end - context "supports arbitrary combinations of the above syntax and features" do + context 'when supporting arbitrary combinations of the above syntax and features' do + let(:foo_bar_api_map) do + api_map = Solargraph::ApiMap.new + source = Solargraph::Source.load_string(%( + module Foo + class Bar + # @return [Bar] + def make_bar + end + end + end + )) + api_map.map source + api_map + end + it 'returns string representations of the entire type array' do type = Solargraph::ComplexType.parse('String', 'Array') expect(type.to_s).to eq('String, Array') @@ -635,21 +651,6 @@ expect(types.to_rbs).to eq('(Array[String] | Hash[String, Symbol] | [String, Integer])') end - let(:foo_bar_api_map) do - api_map = Solargraph::ApiMap.new - source = Solargraph::Source.load_string(%( - module Foo - class Bar - # @return [Bar] - def make_bar - end - end - end - )) - api_map.map source - api_map - end - it 'qualifies types with list parameters' do original = Solargraph::ComplexType.parse('Class').first expect(original).not_to be_rooted diff --git a/spec/convention/activesupport_concern_spec.rb b/spec/convention/activesupport_concern_spec.rb index a0eae979a..2360e2a83 100644 --- a/spec/convention/activesupport_concern_spec.rb +++ b/spec/convention/activesupport_concern_spec.rb @@ -101,7 +101,7 @@ def self.my_method; end # create a temporary directory with the scope of the spec around do |example| require 'tmpdir' - Dir.mktmpdir("rspec-solargraph-") do |dir| + Dir.mktmpdir('rspec-solargraph-') do |dir| @temp_dir = dir example.run end @@ -151,11 +151,11 @@ class Base it { is_expected.not_to be_empty } - it "has one item" do + it 'has one item' do expect(method_pins.size).to eq(1) end - it "is a Pin::Method" do + it 'is a Pin::Method' do expect(method_pins.first).to be_a(Solargraph::Pin::Method) end end diff --git a/spec/convention/struct_definition_spec.rb b/spec/convention/struct_definition_spec.rb index 5c3fc5211..02786cfe6 100644 --- a/spec/convention/struct_definition_spec.rb +++ b/spec/convention/struct_definition_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Convention::StructDefinition do describe 'parsing docs' do it 'supports keyword args' do @@ -103,7 +105,7 @@ expect(params.map(&:name)).to eql(%w[bar baz]) expect(params.map(&:return_type).map(&:tag)).to eql(%w[Integer String]) - expect(params[1].documentation).to eql("Some text") + expect(params[1].documentation).to eql('Some text') end [true, false].each do |kw_args| @@ -116,21 +118,21 @@ Foo = Struct.new(:bar, :baz, keyword_init: #{kw_args}) ), 'test.rb') - params_bar = source.pins.find { |p| p.path == "Foo#bar=" }.parameters + params_bar = source.pins.find { |p| p.path == 'Foo#bar=' }.parameters expect(params_bar.length).to be(1) - expect(params_bar.first.return_type.tag).to eql("String") + expect(params_bar.first.return_type.tag).to eql('String') expect(params_bar.first.arg?).to be(true) - params_baz = source.pins.find { |p| p.path == "Foo#baz=" }.parameters + params_baz = source.pins.find { |p| p.path == 'Foo#baz=' }.parameters expect(params_baz.length).to be(1) - expect(params_baz.first.return_type.tag).to eql("Integer") + expect(params_baz.first.return_type.tag).to eql('Integer') expect(params_baz.first.arg?).to be(true) - iv_bar = source.pins.find { |p| p.name == "@bar" } - expect(iv_bar.return_type.tag).to eql("String") + iv_bar = source.pins.find { |p| p.name == '@bar' } + expect(iv_bar.return_type.tag).to eql('String') - iv_baz = source.pins.find { |p| p.name == "@baz" } - expect(iv_baz.return_type.tag).to eql("Integer") + iv_baz = source.pins.find { |p| p.name == '@baz' } + expect(iv_baz.return_type.tag).to eql('Integer') end end end diff --git a/spec/convention_spec.rb b/spec/convention_spec.rb index b6f4fc52e..7785aef68 100644 --- a/spec/convention_spec.rb +++ b/spec/convention_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Convention do it 'newly defined pins are resolved by ApiMap after file changes' do filename = 'test.rb' diff --git a/spec/diagnostics/base_spec.rb b/spec/diagnostics/base_spec.rb index d2068caf1..417797172 100644 --- a/spec/diagnostics/base_spec.rb +++ b/spec/diagnostics/base_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::Base do - it "returns empty diagnostics" do - reporter = Solargraph::Diagnostics::Base.new + it 'returns empty diagnostics' do + reporter = described_class.new expect(reporter.diagnose(nil, nil)).to be_empty end end diff --git a/spec/diagnostics/require_not_found_spec.rb b/spec/diagnostics/require_not_found_spec.rb index 6ecfdcae9..1588a033b 100644 --- a/spec/diagnostics/require_not_found_spec.rb +++ b/spec/diagnostics/require_not_found_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::RequireNotFound do - before :each do + before do @source = Solargraph::Source.new(%( require 'rexml/document' require 'not_valid' @@ -11,8 +13,8 @@ @api_map.catalog Solargraph::Bench.new(source_maps: [@source_map], external_requires: ['not_valid']) end - it "reports unresolved requires" do - reporter = Solargraph::Diagnostics::RequireNotFound.new + it 'reports unresolved requires' do + reporter = described_class.new result = reporter.diagnose(@source, @api_map) expect(result.length).to eq(1) end diff --git a/spec/diagnostics/rubocop_helpers_spec.rb b/spec/diagnostics/rubocop_helpers_spec.rb index d59dac4ae..7bf374d67 100644 --- a/spec/diagnostics/rubocop_helpers_spec.rb +++ b/spec/diagnostics/rubocop_helpers_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::RubocopHelpers do - context do + context 'with custom version' do around do |example| old_gem_path = Gem.paths.path - custom_gem_path = File.absolute_path('spec/fixtures/rubocop-custom-version').gsub(/\\/, '/') + custom_gem_path = File.absolute_path('spec/fixtures/rubocop-custom-version').gsub('\\', '/') # Remove a post_reset hook set by bundler to restore cached specs # Source: https://github.com/ruby/ruby/blob/master/lib/bundler/rubygems_integration.rb#L487-L489 old_post_reset_hooks = Gem.post_reset_hooks.dup @@ -18,34 +20,34 @@ let(:custom_version) { '0.0.0' } - it "requires the specified version of rubocop" do + it 'requires the specified version of rubocop' do input = custom_version - Solargraph::Diagnostics::RubocopHelpers.require_rubocop(input) + described_class.require_rubocop(input) output = RuboCop::Version::STRING expect(output).to eq(custom_version) end end - context do + context 'with real version' do let(:default_version) { Gem::Specification.find_by_name('rubocop').full_gem_path[/[^-]+$/] } - it "requires the default version of rubocop" do + it 'requires the default version of rubocop' do input = nil - Solargraph::Diagnostics::RubocopHelpers.require_rubocop(input) + described_class.require_rubocop(input) output = RuboCop::Version::STRING expect(output).to eq(default_version) end end - it "converts lower-case drive letters to upper-case" do + it 'converts lower-case drive letters to upper-case' do input = 'c:/one/two' - output = Solargraph::Diagnostics::RubocopHelpers.fix_drive_letter(input) + output = described_class.fix_drive_letter(input) expect(output).to eq('C:/one/two') end - it "ignores paths without drive letters" do + it 'ignores paths without drive letters' do input = 'one/two' - output = Solargraph::Diagnostics::RubocopHelpers.fix_drive_letter(input) + output = described_class.fix_drive_letter(input) expect(output).to eq('one/two') end end diff --git a/spec/diagnostics/rubocop_spec.rb b/spec/diagnostics/rubocop_spec.rb index aa279f66c..7f6a9221d 100644 --- a/spec/diagnostics/rubocop_spec.rb +++ b/spec/diagnostics/rubocop_spec.rb @@ -10,41 +10,41 @@ def bar foo = Foo.new ), 'file.rb') - rubocop = Solargraph::Diagnostics::Rubocop.new + rubocop = described_class.new result = rubocop.diagnose(source, nil) expect(result).to be_a(Array) end - context "with validation error" do + context 'with validation error' do let(:fixture_path) do - File.absolute_path('spec/fixtures/rubocop-validation-error').gsub(/\\/, '/') + File.absolute_path('spec/fixtures/rubocop-validation-error').gsub('\\', '/') end around do |example| config_file = File.join(fixture_path, '.rubocop.yml') File.write(config_file, <<~YAML) - inherit_from: - - file_not_found.yml - YAML + inherit_from: + - file_not_found.yml + YAML example.run ensure - File.delete(config_file) if File.exist?(config_file) + FileUtils.rm_f(config_file) end it 'handles validation errors' do file = File.realpath(File.join(fixture_path, 'app.rb')) source = Solargraph::Source.load(file) - rubocop = Solargraph::Diagnostics::Rubocop.new - expect { + rubocop = described_class.new + expect do rubocop.diagnose(source, nil) - }.to raise_error(Solargraph::DiagnosticsError) + end.to raise_error(Solargraph::DiagnosticsError) end end it 'calculates ranges' do file = File.realpath(File.join('spec', 'fixtures', 'rubocop-unused-variable-error', 'app.rb')) source = Solargraph::Source.load(file) - rubocop = Solargraph::Diagnostics::Rubocop.new + rubocop = described_class.new results = rubocop.diagnose(source, nil) expect(results).to be_one diff --git a/spec/diagnostics/type_check_spec.rb b/spec/diagnostics/type_check_spec.rb index e343d6598..f28cc7b73 100644 --- a/spec/diagnostics/type_check_spec.rb +++ b/spec/diagnostics/type_check_spec.rb @@ -1,29 +1,31 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::TypeCheck do let(:api_map) { Solargraph::ApiMap.new } - it "detects defined return types" do + it 'detects defined return types' do source = Solargraph::Source.load_string(%( # @return [String] def foo end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end - it "detects missing return types" do + it 'detects missing return types' do source = Solargraph::Source.load_string(%( def foo end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always', 'strong').diagnose(source, api_map) + result = described_class.new('always', 'strong').diagnose(source, api_map) expect(result.length).to eq(1) expect(result[0][:message]).to include('foo') end - it "detects defined parameter types" do + it 'detects defined parameter types' do source = Solargraph::Source.load_string(%( # @param bar [String] # @return [String] @@ -31,11 +33,11 @@ def foo(bar) end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end - it "detects missing parameter types" do + it 'detects missing parameter types' do source = Solargraph::Source.load_string(%( # @return [String] def foo(bar) @@ -43,12 +45,12 @@ def foo(bar) end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always', 'strong').diagnose(source, api_map) + result = described_class.new('always', 'strong').diagnose(source, api_map) expect(result.length).to eq(1) expect(result[0][:message]).to include('bar') end - it "detects return types from superclasses" do + it 'detects return types from superclasses' do source = Solargraph::Source.load_string(%( class First # @return [String] @@ -61,11 +63,11 @@ def foo end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end - it "detects parameter types from superclasses" do + it 'detects parameter types from superclasses' do source = Solargraph::Source.load_string(%( class First # @param bar [String] @@ -79,11 +81,11 @@ def foo bar end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end - it "works with optional and keyword arguments" do + it 'works with optional and keyword arguments' do source = Solargraph::Source.load_string(%( # @param bar [String] # @param baz [String] @@ -92,7 +94,7 @@ def foo(bar = 'bar', baz: 'baz') end )) api_map.map source - result = Solargraph::Diagnostics::TypeCheck.new('always').diagnose(source, api_map) + result = described_class.new('always').diagnose(source, api_map) expect(result).to be_empty end end diff --git a/spec/diagnostics/update_errors_spec.rb b/spec/diagnostics/update_errors_spec.rb index 11b8c6f8f..0bb013231 100644 --- a/spec/diagnostics/update_errors_spec.rb +++ b/spec/diagnostics/update_errors_spec.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics::UpdateErrors do - it "detects repaired lines" do + it 'detects repaired lines' do api_map = Solargraph::ApiMap.new orig = Solargraph::Source.load_string('foo', 'test.rb') - diagnoser = Solargraph::Diagnostics::UpdateErrors.new + diagnoser = described_class.new result = diagnoser.diagnose(orig, api_map) expect(result.length).to eq(0) updater = Solargraph::Source::Updater.new('test.rb', 2, [ - Solargraph::Source::Change.new( - Solargraph::Range.from_to(0, 3, 0, 3), - '.' - ) - ]) + Solargraph::Source::Change.new( + Solargraph::Range.from_to(0, 3, 0, 3), + '.' + ) + ]) source = orig.synchronize(updater) - diagnoser = Solargraph::Diagnostics::UpdateErrors.new + diagnoser = described_class.new result = diagnoser.diagnose(source, api_map) expect(result.length).to eq(1) end diff --git a/spec/diagnostics_spec.rb b/spec/diagnostics_spec.rb index ba689692d..83e222e12 100644 --- a/spec/diagnostics_spec.rb +++ b/spec/diagnostics_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + describe Solargraph::Diagnostics do - it "registers reporters" do - Solargraph::Diagnostics.register 'base', Solargraph::Diagnostics::Base - expect(Solargraph::Diagnostics.reporters).to include('base') - expect(Solargraph::Diagnostics.reporter('base')).to be(Solargraph::Diagnostics::Base) + it 'registers reporters' do + described_class.register 'base', Solargraph::Diagnostics::Base + expect(described_class.reporters).to include('base') + expect(described_class.reporter('base')).to be(Solargraph::Diagnostics::Base) end end diff --git a/spec/doc_map_spec.rb b/spec/doc_map_spec.rb index 7c541da27..8ff1e70b1 100644 --- a/spec/doc_map_spec.rb +++ b/spec/doc_map_spec.rb @@ -33,16 +33,12 @@ end end - context 'understands rspec + rspec-mocks require pattern' do + context 'when understanding rspec + rspec-mocks require pattern' do let(:requires) do ['rspec-mocks'] end - # This is a gem name vs require name issue - works under - # solargraph-rspec, but not without - xit 'generates pins from gems' do - pending('handling dependencies from conventions as gem names, not requires') - + it 'generates pins from gems' do ns_pin = doc_map.pins.find { |pin| pin.path == 'RSpec::Mocks' } expect(ns_pin).to be_a(Solargraph::Pin::Namespace) end @@ -53,22 +49,18 @@ ['not_a_gem'] end - # expected: ["not_a_gem"] - # got: ["not_a_gem", "rspec-mocks"] - # - # This is a gem name vs require name issue coming from conventions - # - will pass once the above context passes - xit 'tracks unresolved requires' do + it 'tracks unresolved requires' do # These are auto-required by solargraph-rspec in case the bundle # includes these gems. In our case, it doesn't! - unprovided_solargraph_rspec_requires = [ - 'rspec-rails', - 'actionmailer', - 'activerecord', - 'shoulda-matchers', - 'rspec-sidekiq', - 'airborne', - 'activesupport' + unprovided_solargraph_rspec_requires = %w[ + rspec-rails + actionmailer + actionpack + activerecord + shoulda-matchers + rspec-sidekiq + airborne + activesupport ] expect(doc_map.unresolved_requires - unprovided_solargraph_rspec_requires) .to eq(['not_a_gem']) @@ -79,7 +71,7 @@ # Requiring 'set' is unnecessary because it's already included in core. It # might make sense to log redundant requires, but a warning is overkill. allow(Solargraph.logger).to receive(:warn).and_call_original - Solargraph::DocMap.new(['set'], workspace) + described_class.new(['set'], workspace) expect(Solargraph.logger).not_to have_received(:warn).with(/path set/) end @@ -172,16 +164,16 @@ it 'includes convention requires from environ' do dummy_convention = Class.new(Solargraph::Convention::Base) do - def global(doc_map) + def global doc_map Solargraph::Environ.new( - requires: ['convention_gem1', 'convention_gem2'] + requires: %w[convention_gem1 convention_gem2] ) end end Solargraph::Convention.register dummy_convention - doc_map = Solargraph::DocMap.new(['original_gem'], workspace) + doc_map = described_class.new(['original_gem'], workspace) # @todo this should probably not be in requires, which is a # path, and instead be in a new gem_names property on the diff --git a/spec/language_server/host/diagnoser_spec.rb b/spec/language_server/host/diagnoser_spec.rb index 697d352bd..9ce9df627 100644 --- a/spec/language_server/host/diagnoser_spec.rb +++ b/spec/language_server/host/diagnoser_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Host::Diagnoser do - it "diagnoses on ticks" do - host = double(Solargraph::LanguageServer::Host, options: { 'diagnostics' => true }, synchronizing?: false) + it 'diagnoses on ticks' do + host = instance_double(Solargraph::LanguageServer::Host, options: { 'diagnostics' => true }, synchronizing?: false) allow(host).to receive(:diagnose) - diagnoser = Solargraph::LanguageServer::Host::Diagnoser.new(host) + diagnoser = described_class.new(host) diagnoser.schedule 'file.rb' diagnoser.tick expect(host).to have_received(:diagnose).with('file.rb') diff --git a/spec/language_server/host/dispatch_spec.rb b/spec/language_server/host/dispatch_spec.rb index 9b314c403..8db9c1246 100644 --- a/spec/language_server/host/dispatch_spec.rb +++ b/spec/language_server/host/dispatch_spec.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Host::Dispatch do before :all do # @dispatch = Solargraph::LanguageServer::Host::Dispatch @dispatch = Object.new - @dispatch.extend Solargraph::LanguageServer::Host::Dispatch + @dispatch.extend described_class end - after :each do + after do @dispatch.libraries.clear @dispatch.sources.clear end - it "finds an explicit library" do + it 'finds an explicit library' do @dispatch.libraries.push Solargraph::Library.load('*') src = @dispatch.sources.open('file:///file.rb', 'a=b', 0) @dispatch.libraries.first.merge src @@ -18,7 +20,7 @@ expect(lib).to be(@dispatch.libraries.first) end - it "finds an implicit library" do + it 'finds an implicit library' do dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) file = File.join(dir, 'new.rb') uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) @@ -28,7 +30,7 @@ expect(lib).to be(@dispatch.libraries.first) end - it "finds a generic library" do + it 'finds a generic library' do dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) file = '/external.rb' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) diff --git a/spec/language_server/host/message_worker_spec.rb b/spec/language_server/host/message_worker_spec.rb index 5e5bef481..4b10f0265 100644 --- a/spec/language_server/host/message_worker_spec.rb +++ b/spec/language_server/host/message_worker_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Host::MessageWorker do - it "handle requests on queue" do - host = double(Solargraph::LanguageServer::Host) - message = {'method' => '$/example'} + it 'handle requests on queue' do + host = instance_double(Solargraph::LanguageServer::Host) + message = { 'method' => '$/example' } allow(host).to receive(:receive).with(message).and_return(nil) - worker = Solargraph::LanguageServer::Host::MessageWorker.new(host) + worker = described_class.new(host) worker.queue(message) expect(worker.messages).to eq [message] worker.tick diff --git a/spec/language_server/host_spec.rb b/spec/language_server/host_spec.rb index 5635680e3..f0497b8f3 100644 --- a/spec/language_server/host_spec.rb +++ b/spec/language_server/host_spec.rb @@ -1,16 +1,18 @@ +# frozen_string_literal: true + require 'tmpdir' describe Solargraph::LanguageServer::Host do - it "prepares a workspace" do - host = Solargraph::LanguageServer::Host.new + it 'prepares a workspace' do + host = described_class.new Dir.mktmpdir do |dir| - host.prepare (dir) - expect(host.libraries.first).not_to be(nil) + host.prepare(dir) + expect(host.libraries.first).not_to be_nil end end - it "processes responses to message requests" do - host = Solargraph::LanguageServer::Host.new + it 'processes responses to message requests' do + host = described_class.new done_somethings = 0 host.send_request 'window/showMessageRequest', { 'message' => 'Message', @@ -20,15 +22,15 @@ end expect(host.pending_requests.length).to eq(1) host.receive({ - 'id' => host.pending_requests.first, - 'result' => 'Do something' - }) + 'id' => host.pending_requests.first, + 'result' => 'Do something' + }) expect(done_somethings).to eq(1) end - it "creates files from disk" do + it 'creates files from disk' do Dir.mktmpdir do |dir| - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.prepare dir file = File.join(dir, 'test.rb') File.write(file, "foo = 'foo'") @@ -38,34 +40,34 @@ end end - it "deletes files" do + it 'deletes files' do Dir.mktmpdir do |dir| - expect { - host = Solargraph::LanguageServer::Host.new + expect do + host = described_class.new file = File.join(dir, 'test.rb') File.write(file, "foo = 'foo'") host.prepare dir uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) host.delete(uri) - }.not_to raise_error + end.not_to raise_error end end - it "cancels requests" do - host = Solargraph::LanguageServer::Host.new + it 'cancels requests' do + host = described_class.new host.cancel 1 expect(host.cancel?(1)).to be(true) end - it "runs diagnostics on opened files" do + it 'runs diagnostics on opened files' do Dir.mktmpdir do |dir| - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.configure({ 'diagnostics' => true }) file = File.join(dir, 'test.rb') File.write(file, "foo = 'foo'") host.start host.prepare dir - uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) + Solargraph::LanguageServer::UriHelpers.file_to_uri(file) host.open(file, File.read(file), 1) buffer = host.flush times = 0 @@ -81,30 +83,28 @@ end end - it "handles DiagnosticsErrors" do - host = Solargraph::LanguageServer::Host.new - library = double(:Library) + it 'handles DiagnosticsErrors' do + host = described_class.new + library = instance_double(Solargraph::Library) allow(library).to receive(:diagnose).and_raise(Solargraph::DiagnosticsError) - allow(library).to receive(:contain?).and_return(true) - allow(library).to receive(:synchronized?).and_return(true) - allow(library).to receive(:mapped?).and_return(true) + allow(library).to receive_messages(contain?: true, synchronized?: true, mapped?: true) allow(library).to receive(:attach) allow(library).to receive(:merge) allow(library).to receive(:catalog) # @todo Smelly instance variable access host.instance_variable_set(:@libraries, [library]) host.open('file:///test.rb', '', 0) - expect { + expect do host.diagnose 'file:///test.rb' - }.not_to raise_error + end.not_to raise_error result = host.flush expect(result).to include('Error in diagnostics') end - it "opens multiple folders" do - host = Solargraph::LanguageServer::Host.new - app1_folder = File.absolute_path('spec/fixtures/workspace_folders/folder1').gsub(/\\/, '/') - app2_folder = File.absolute_path('spec/fixtures/workspace_folders/folder2').gsub(/\\/, '/') + it 'opens multiple folders' do + host = described_class.new + app1_folder = File.absolute_path('spec/fixtures/workspace_folders/folder1').gsub('\\', '/') + app2_folder = File.absolute_path('spec/fixtures/workspace_folders/folder2').gsub('\\', '/') host.prepare(app1_folder) host.prepare(app2_folder) file1_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri("#{app1_folder}/app.rb") @@ -119,27 +119,27 @@ expect(app2_map).not_to include('Folder1App') end - it "stops" do - host = Solargraph::LanguageServer::Host.new + it 'stops' do + host = described_class.new host.stop expect(host.stopped?).to be(true) end - it "retains orphaned sources" do + it 'retains orphaned sources' do dir = File.absolute_path('spec/fixtures/workspace') file = File.join(dir, 'lib', 'thing.rb') file_uri = Solargraph::LanguageServer::UriHelpers.uri_to_file(file) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.prepare(dir) host.open(file_uri, File.read(file), 1) host.remove(dir) - expect{ + expect do host.document_symbols(file_uri) - }.not_to raise_error + end.not_to raise_error end - it "responds with empty diagnostics for unopened files" do - host = Solargraph::LanguageServer::Host.new + it 'responds with empty diagnostics for unopened files' do + host = described_class.new host.diagnose 'file:///file.rb' response = host.flush json = JSON.parse(response.lines.last) @@ -147,48 +147,48 @@ expect(json['params']['diagnostics']).to be_empty end - it "rescues runtime errors from messages" do - host = Solargraph::LanguageServer::Host.new + it 'rescues runtime errors from messages' do + host = described_class.new message_class = Class.new(Solargraph::LanguageServer::Message::Base) do def process - raise RuntimeError, 'Always raise an error from this message' + raise 'Always raise an error from this message' end end Solargraph::LanguageServer::Message.register('raiseRuntimeError', message_class) - expect { + expect do host.receive({ - 'id' => 1, - 'method' => 'raiseRuntimeError', - 'params' => {} - }) - }.not_to raise_error + 'id' => 1, + 'method' => 'raiseRuntimeError', + 'params' => {} + }) + end.not_to raise_error end - it "ignores invalid messages" do - host = Solargraph::LanguageServer::Host.new - expect { + it 'ignores invalid messages' do + host = described_class.new + expect do host.receive({ 'bad' => 'message' }) - }.not_to raise_error + end.not_to raise_error end it 'repairs simple breaking changes without incremental sync' do file = '/test.rb' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.prepare '' host.open uri, 'Foo::Bar', 1 sleep 0.1 until host.libraries.all?(&:mapped?) host.change({ - "textDocument" => { - "uri" => uri, - 'version' => 2 - }, - "contentChanges" => [ - { - "text" => "Foo::Bar." - } - ] - }) + 'textDocument' => { + 'uri' => uri, + 'version' => 2 + }, + 'contentChanges' => [ + { + 'text' => 'Foo::Bar.' + } + ] + }) source = host.sources.find(uri) # @todo Smelly private method access expect(source.send(:repaired)).to eq('Foo::Bar ') @@ -206,76 +206,76 @@ def initialize(foo); end file = '/test.rb' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.prepare '' host.open uri, code, 1 sleep 0.1 until host.libraries.all?(&:mapped?) result = host.locate_pins({ - "data" => { - "uri" => uri, - "location" => { - "range" => { - "start" => { - "line" => 5, - "character" => 12 - }, - "end" => { - "line" => 5, - "character" => 15 - } - } - }, - "path" => "Example.new" - } - }) + 'data' => { + 'uri' => uri, + 'location' => { + 'range' => { + 'start' => { + 'line' => 5, + 'character' => 12 + }, + 'end' => { + 'line' => 5, + 'character' => 15 + } + } + }, + 'path' => 'Example.new' + } + }) expect(result.map(&:path)).to include('Example.new') end end describe '#references_from' do it 'rescues FileNotFound errors' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new expect { host.references_from('file:///not_a_file.rb', 1, 1) }.not_to raise_error end it 'logs FileNotFound errors' do allow(Solargraph.logger).to receive(:warn) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.references_from('file:///not_a_file.rb', 1, 1) expect(Solargraph.logger).to have_received(:warn).with(/FileNotFoundError/) end it 'rescues InvalidOffset errors' do - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.open('file:///file.rb', 'class Foo; end', 1) expect { host.references_from('file:///file.rb', 0, 100) }.not_to raise_error end it 'logs InvalidOffset errors' do allow(Solargraph.logger).to receive(:warn) - host = Solargraph::LanguageServer::Host.new + host = described_class.new host.open('file:///file.rb', 'class Foo; end', 1) host.references_from('file:///file.rb', 0, 100) expect(Solargraph.logger).to have_received(:warn).with(/InvalidOffsetError/) end end - describe "Workspace variations" do - before :each do - @host = Solargraph::LanguageServer::Host.new + describe 'Workspace variations' do + before do + @host = described_class.new end - after :each do + after do @host.stop end - it "creates a library for a file without a workspace" do + it 'creates a library for a file without a workspace' do @host.open('file:///file.rb', 'class Foo; end', 1) symbols = @host.document_symbols('file:///file.rb') expect(symbols).not_to be_empty end - it "opens a file outside of prepared libraries" do + it 'opens a file outside of prepared libraries' do @host.prepare(File.absolute_path(File.join('spec', 'fixtures', 'workspace'))) @host.open('file:///file.rb', 'class Foo; end', 1) symbols = @host.document_symbols('file:///file.rb') diff --git a/spec/language_server/message/completion_item/resolve_spec.rb b/spec/language_server/message/completion_item/resolve_spec.rb index 3ae66dbda..9e270b4e9 100644 --- a/spec/language_server/message/completion_item/resolve_spec.rb +++ b/spec/language_server/message/completion_item/resolve_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::CompletionItem::Resolve do - it "returns MarkupContent for documentation" do + it 'returns MarkupContent for documentation' do pin = Solargraph::Pin::Method.new( location: nil, closure: Solargraph::Pin::Namespace.new(name: 'Foo'), @@ -9,26 +11,26 @@ visibility: :public, parameters: [] ) - host = double(Solargraph::LanguageServer::Host, locate_pins: [pin], probe: pin, detail: nil, options: { 'enablePages' => true }) - resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { - 'params' => pin.completion_item - }) + host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin], options: { 'enablePages' => true }) + resolve = described_class.new(host, { + 'params' => pin.completion_item + }) resolve.process expect(resolve.result[:documentation][:kind]).to eq('markdown') expect(resolve.result[:documentation][:value]).to include('A method') end - it "returns nil documentation for empty strings" do + it 'returns nil documentation for empty strings' do pin = Solargraph::Pin::InstanceVariable.new( location: nil, closure: Solargraph::Pin::Namespace.new(name: 'Foo'), name: '@bar', comments: '' ) - host = double(Solargraph::LanguageServer::Host, locate_pins: [pin], probe: pin, detail: nil) - resolve = Solargraph::LanguageServer::Message::CompletionItem::Resolve.new(host, { - 'params' => pin.completion_item - }) + host = instance_double(Solargraph::LanguageServer::Host, locate_pins: [pin]) + resolve = described_class.new(host, { + 'params' => pin.completion_item + }) resolve.process expect(resolve.result[:documentation]).to be_nil end diff --git a/spec/language_server/message/extended/check_gem_version_spec.rb b/spec/language_server/message/extended/check_gem_version_spec.rb index 935917442..26023f505 100644 --- a/spec/language_server/message/extended/check_gem_version_spec.rb +++ b/spec/language_server/message/extended/check_gem_version_spec.rb @@ -1,40 +1,43 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::Extended::CheckGemVersion do - before :each do - version = double(:GemVersion, version: Gem::Version.new('1.0.0')) - Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = double(:fetcher, search_for_dependency: [version]) + before do + version = instance_double(Gem::Version, version: Gem::Version.new('1.0.0')) + described_class.fetcher = + instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end - after :each do - Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = nil + after do + described_class.fetcher = nil end - it "checks the gem source" do + it 'checks the gem source' do host = Solargraph::LanguageServer::Host.new message = described_class.new(host, {}) expect { message.process }.not_to raise_error end - it "performs a verbose check" do + it 'performs a verbose check' do host = Solargraph::LanguageServer::Host.new message = described_class.new(host, { 'params' => { 'verbose' => true } }) expect { message.process }.not_to raise_error end - it "detects available updates" do + it 'detects available updates' do host = Solargraph::LanguageServer::Host.new message = described_class.new(host, {}, current: Gem::Version.new('0.0.1')) expect { message.process }.not_to raise_error end - it "performs a verbose check with an available update" do + it 'performs a verbose check with an available update' do host = Solargraph::LanguageServer::Host.new message = described_class.new(host, { 'params' => { 'verbose' => true } }, current: Gem::Version.new('0.0.1')) expect { message.process }.not_to raise_error end - it "responds to update actions" do + it 'responds to update actions' do host = Solargraph::LanguageServer::Host.new - message = Solargraph::LanguageServer::Message::Extended::CheckGemVersion.new(host, {}, current: Gem::Version.new('0.0.1')) + message = described_class.new(host, {}, current: Gem::Version.new('0.0.1')) message.process response = nil reader = Solargraph::LanguageServer::Transport::DataReader.new @@ -42,19 +45,19 @@ response = data end reader.receive host.flush - expect { + expect do action = { - "id" => response['id'], - "result" => response['params']['actions'].first + 'id' => response['id'], + 'result' => response['params']['actions'].first } host.receive action - }.not_to raise_error + end.not_to raise_error end it 'uses bundler' do host = Solargraph::LanguageServer::Host.new - host.configure({'useBundler' => true}) - message = Solargraph::LanguageServer::Message::Extended::CheckGemVersion.new(host, {}, current: Gem::Version.new('0.0.1')) + host.configure({ 'useBundler' => true }) + message = described_class.new(host, {}, current: Gem::Version.new('0.0.1')) message.process response = nil reader = Solargraph::LanguageServer::Transport::DataReader.new @@ -62,12 +65,12 @@ response = data end reader.receive host.flush - expect { + expect do action = { - "id" => response['id'], - "result" => response['params']['actions'].first + 'id' => response['id'], + 'result' => response['params']['actions'].first } host.receive action - }.not_to raise_error + end.not_to raise_error end end diff --git a/spec/language_server/message/initialize_spec.rb b/spec/language_server/message/initialize_spec.rb index 3ab54e2de..aae61cff7 100644 --- a/spec/language_server/message/initialize_spec.rb +++ b/spec/language_server/message/initialize_spec.rb @@ -1,94 +1,98 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::Initialize do - it "prepares workspace folders" do + it 'prepares workspace folders' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) - init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'workspaceFolders' => [ - { - 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir), - 'name' => 'workspace' - } - ] - } - }) + init = described_class.new(host, { + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'workspaceFolders' => [ + { + 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir), + 'name' => 'workspace' + } + ] + } + }) init.process expect(host.folders.length).to eq(1) end - it "prepares rootUri as a workspace" do + it 'prepares rootUri as a workspace' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) - init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'rootUri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) - } - }) + init = described_class.new(host, { + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'rootUri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) + } + }) init.process expect(host.folders.length).to eq(1) end - it "prepares rootPath as a workspace" do + it 'prepares rootPath as a workspace' do host = Solargraph::LanguageServer::Host.new dir = File.realpath(File.join('spec', 'fixtures', 'workspace')) - init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'capabilities' => { - 'workspace' => { - 'workspaceFolders' => true - } - }, - 'rootPath' => dir - } - }) + init = described_class.new(host, { + 'params' => { + 'capabilities' => { + 'workspace' => { + 'workspaceFolders' => true + } + }, + 'rootPath' => dir + } + }) init.process expect(host.folders.length).to eq(1) end it 'returns the default capabilities' do host = Solargraph::LanguageServer::Host.new - init = Solargraph::LanguageServer::Message::Initialize.new(host, {}) + init = described_class.new(host, {}) init.process result = init.result expect(result).to include(:capabilities) expect(result[:capabilities]).to eq({ - textDocumentSync: 2, - workspace: { workspaceFolders: { supported: true, changeNotifications: true } }, - completionProvider: { resolveProvider: true, triggerCharacters: ['.', ':', '@'] }, - signatureHelpProvider: { triggerCharacters: ['(', ','] }, - hoverProvider: true, - documentSymbolProvider: true, - definitionProvider: true, - typeDefinitionProvider: true, - renameProvider: { prepareProvider: true }, - referencesProvider: true, - workspaceSymbolProvider: true, - foldingRangeProvider: true, - documentHighlightProvider: true - }) + textDocumentSync: 2, + workspace: { workspaceFolders: { supported: true, + changeNotifications: true } }, + completionProvider: { resolveProvider: true, + triggerCharacters: ['.', ':', '@'] }, + signatureHelpProvider: { triggerCharacters: ['(', ','] }, + hoverProvider: true, + documentSymbolProvider: true, + definitionProvider: true, + typeDefinitionProvider: true, + renameProvider: { prepareProvider: true }, + referencesProvider: true, + workspaceSymbolProvider: true, + foldingRangeProvider: true, + documentHighlightProvider: true + }) end it 'returns all capabilities when all options are enabled' do host = Solargraph::LanguageServer::Host.new - init = Solargraph::LanguageServer::Message::Initialize.new(host, { - 'params' => { - 'initializationOptions' => { - 'completion' => true, - 'autoformat' => true, - 'formatting' => true - } - } - }) + init = described_class.new(host, { + 'params' => { + 'initializationOptions' => { + 'completion' => true, + 'autoformat' => true, + 'formatting' => true + } + } + }) init.process result = init.result diff --git a/spec/language_server/message/text_document/definition_spec.rb b/spec/language_server/message/text_document/definition_spec.rb index b6c98b99b..d84d23cbe 100644 --- a/spec/language_server/message/text_document/definition_spec.rb +++ b/spec/language_server/message/text_document/definition_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::TextDocument::Definition do it 'prepares empty directory' do Dir.mktmpdir do |dir| @@ -11,7 +13,7 @@ host.catalog file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(test_rb_path) other_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(thing_rb_path) - message = Solargraph::LanguageServer::Message::TextDocument::Definition + message = described_class .new(host, { 'params' => { 'textDocument' => { @@ -35,17 +37,17 @@ host.catalog file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/other.rb')) other_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/thing.rb')) - message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => file_uri - }, - 'position' => { - 'line' => 4, - 'character' => 10 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => file_uri + }, + 'position' => { + 'line' => 4, + 'character' => 10 + } + } + }) message.process expect(message.result.first[:uri]).to eq(other_uri) end @@ -56,18 +58,21 @@ host.prepare(path) sleep 0.1 until host.libraries.all?(&:mapped?) host.catalog - message = Solargraph::LanguageServer::Message::TextDocument::Definition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join(path, 'lib', 'other.rb')) - }, - 'position' => { - 'line' => 0, - 'character' => 10 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join( + path, 'lib', 'other.rb' + )) + }, + 'position' => { + 'line' => 0, + 'character' => 10 + } + } + }) message.process - expect(message.result.first[:uri]).to eq(Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join(path, 'lib', 'thing.rb'))) + expect(message.result.first[:uri]).to eq(Solargraph::LanguageServer::UriHelpers.file_to_uri(File.join(path, 'lib', + 'thing.rb'))) end end diff --git a/spec/language_server/message/text_document/formatting_spec.rb b/spec/language_server/message/text_document/formatting_spec.rb index 10797e8db..65c0841d7 100644 --- a/spec/language_server/message/text_document/formatting_spec.rb +++ b/spec/language_server/message/text_document/formatting_spec.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::TextDocument::Formatting do it 'gracefully handles empty files' do - host = double(:Host, read_text: '', formatter_config: {}) + host = instance_double(Solargraph::LanguageServer::Host, read_text: '', formatter_config: {}) request = { 'params' => { - 'textDocument' => { + 'textDocument' => { 'uri' => 'test.rb' } } } - message = Solargraph::LanguageServer::Message::TextDocument::Formatting.new(host, request) + message = described_class.new(host, request) message.process expect(message.process.first[:newText]).to be_empty end diff --git a/spec/language_server/message/text_document/hover_spec.rb b/spec/language_server/message/text_document/hover_spec.rb index 74eaee597..76b3c9082 100644 --- a/spec/language_server/message/text_document/hover_spec.rb +++ b/spec/language_server/message/text_document/hover_spec.rb @@ -1,20 +1,22 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::TextDocument::Hover do it 'returns nil for empty documentation' do host = Solargraph::LanguageServer::Host.new host.prepare('spec/fixtures/workspace') sleep 0.1 until host.libraries.all?(&:mapped?) host.catalog - message = Solargraph::LanguageServer::Message::TextDocument::Hover.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => 'file://spec/fixtures/workspace/lib/other.rb' - }, - 'position' => { - 'line' => 5, - 'character' => 0 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => 'file://spec/fixtures/workspace/lib/other.rb' + }, + 'position' => { + 'line' => 5, + 'character' => 0 + } + } + }) message.process expect(message.result).to be_nil end @@ -29,17 +31,17 @@ def foo host = Solargraph::LanguageServer::Host.new host.open('file:///test.rb', code, 1) host.catalog - message = Solargraph::LanguageServer::Message::TextDocument::Hover.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => 'file:///test.rb' - }, - 'position' => { - 'line' => 4, - 'character' => 6 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => 'file:///test.rb' + }, + 'position' => { + 'line' => 4, + 'character' => 6 + } + } + }) message.process expect(message.result[:contents][:value]).to eq("x\n\n`=~ String`") end diff --git a/spec/language_server/message/text_document/rename_spec.rb b/spec/language_server/message/text_document/rename_spec.rb index b16110455..19903eaf9 100644 --- a/spec/language_server/message/text_document/rename_spec.rb +++ b/spec/language_server/message/text_document/rename_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true describe Solargraph::LanguageServer::Message::TextDocument::Rename do - it "renames a symbol" do + it 'renames a symbol' do host = Solargraph::LanguageServer::Host.new host.start host.open('file:///file.rb', %( @@ -10,25 +10,25 @@ class Foo foo = Foo.new ), 1) sleep 0.01 until host.libraries.all?(&:mapped?) - rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 1, - 'character' => 12 - }, - 'newName' => 'Bar' - } - }) + rename = described_class.new(host, { + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 1, + 'character' => 12 + }, + 'newName' => 'Bar' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(2) end - it "renames an argument symbol from method signature" do + it 'renames an argument symbol from method signature' do host = Solargraph::LanguageServer::Host.new host.start host.open('file:///file.rb', %( @@ -40,25 +40,25 @@ def foo(bar) end ), 1) - rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 2, - 'character' => 14 - }, - 'newName' => 'baz' - } - }) + rename = described_class.new(host, { + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 2, + 'character' => 14 + }, + 'newName' => 'baz' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(3) end - it "renames an argument symbol from method body" do + it 'renames an argument symbol from method body' do host = Solargraph::LanguageServer::Host.new host.start host.open('file:///file.rb', %( @@ -69,25 +69,25 @@ def foo(bar) end end ), 1) - rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 3, - 'character' => 6 - }, - 'newName' => 'baz' - } - }) + rename = described_class.new(host, { + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 3, + 'character' => 6 + }, + 'newName' => 'baz' + } + }) rename.process expect(rename.result[:changes]['file:///file.rb'].length).to eq(3) end - it "renames namespace symbol with proper range" do + it 'renames namespace symbol with proper range' do host = Solargraph::LanguageServer::Host.new host.start host.open('file:///file.rb', %( @@ -96,20 +96,20 @@ class Namespace::ExampleClass end obj = Namespace::ExampleClass.new ), 1) - rename = Solargraph::LanguageServer::Message::TextDocument::Rename.new(host, { - 'id' => 1, - 'method' => 'textDocument/rename', - 'params' => { - 'textDocument' => { - 'uri' => 'file:///file.rb' - }, - 'position' => { - 'line' => 2, - 'character' => 12 - }, - 'newName' => 'Nameplace' - } - }) + rename = described_class.new(host, { + 'id' => 1, + 'method' => 'textDocument/rename', + 'params' => { + 'textDocument' => { + 'uri' => 'file:///file.rb' + }, + 'position' => { + 'line' => 2, + 'character' => 12 + }, + 'newName' => 'Nameplace' + } + }) rename.process changes = rename.result[:changes]['file:///file.rb'] expect(changes.length).to eq(3) diff --git a/spec/language_server/message/text_document/type_definition_spec.rb b/spec/language_server/message/text_document/type_definition_spec.rb index 2f7ec3668..16f7f3006 100644 --- a/spec/language_server/message/text_document/type_definition_spec.rb +++ b/spec/language_server/message/text_document/type_definition_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message::TextDocument::TypeDefinition do it 'finds definitions of methods' do host = Solargraph::LanguageServer::Host.new @@ -6,17 +8,17 @@ host.catalog file_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/other.rb')) something_uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(File.absolute_path('spec/fixtures/workspace/lib/something.rb')) - message = Solargraph::LanguageServer::Message::TextDocument::TypeDefinition.new(host, { - 'params' => { - 'textDocument' => { - 'uri' => file_uri - }, - 'position' => { - 'line' => 4, - 'character' => 10 - } - } - }) + message = described_class.new(host, { + 'params' => { + 'textDocument' => { + 'uri' => file_uri + }, + 'position' => { + 'line' => 4, + 'character' => 10 + } + } + }) message.process expect(message.result.first[:uri]).to eq(something_uri) end diff --git a/spec/language_server/message/workspace/did_change_configuration_spec.rb b/spec/language_server/message/workspace/did_change_configuration_spec.rb index 0a082875d..a352a5c85 100644 --- a/spec/language_server/message/workspace/did_change_configuration_spec.rb +++ b/spec/language_server/message/workspace/did_change_configuration_spec.rb @@ -71,8 +71,8 @@ described_class.new(host, message).process expect( - host.registered?('textDocument/completion') - ).to be_truthy, 'Expected textDocument/completion to be registered' + host + ).to be_registered('textDocument/completion'), 'Expected textDocument/completion to be registered' end end @@ -83,8 +83,8 @@ described_class.new(host, message).process expect( - host.registered?('textDocument/completion') - ).to be_falsy, 'Expected textDocument/completion to not be registered' + host + ).not_to be_registered('textDocument/completion'), 'Expected textDocument/completion to not be registered' end end end diff --git a/spec/language_server/message/workspace/did_change_watched_files_spec.rb b/spec/language_server/message/workspace/did_change_watched_files_spec.rb index b78aa06d3..ebe76fc50 100644 --- a/spec/language_server/message/workspace/did_change_watched_files_spec.rb +++ b/spec/language_server/message/workspace/did_change_watched_files_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'tmpdir' describe Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles do @@ -8,17 +10,17 @@ file = File.join(dir, 'foo.rb') File.write file, 'class Foo; end' uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) - changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CREATED, - 'uri' => uri - } - ] - } - }) + changed = described_class.new(host, { + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CREATED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) expect(host.library_for(uri)).to be_a(Solargraph::Library) @@ -33,22 +35,22 @@ uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) host = Solargraph::LanguageServer::Host.new host.prepare dir - changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::DELETED, - 'uri' => uri - } - ] - } - }) + changed = described_class.new(host, { + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::DELETED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) - expect { + expect do host.library_for(uri) - }.to raise_error(Solargraph::FileNotFoundError) + end.to raise_error(Solargraph::FileNotFoundError) end end @@ -60,17 +62,17 @@ host = Solargraph::LanguageServer::Host.new host.prepare dir File.write file, 'class FooBar; end' - changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CHANGED, - 'uri' => uri - } - ] - } - }) + changed = described_class.new(host, { + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles::CHANGED, + 'uri' => uri + } + ] + } + }) changed.process expect(host.synchronizing?).to be(false) library = host.library_for(uri) @@ -81,20 +83,20 @@ end it 'sets errors for invalid change types' do - host = double(Solargraph::LanguageServer::Host, catalog: nil) + host = instance_double(Solargraph::LanguageServer::Host, catalog: nil) allow(host).to receive(:create) allow(host).to receive(:delete) - changed = Solargraph::LanguageServer::Message::Workspace::DidChangeWatchedFiles.new(host, { - 'method' => 'workspace/didChangeWatchedFiles', - 'params' => { - 'changes' => [ - { - 'type' => -1, - 'uri' => 'file:///foo.rb' - } - ] - } - }) + changed = described_class.new(host, { + 'method' => 'workspace/didChangeWatchedFiles', + 'params' => { + 'changes' => [ + { + 'type' => -1, + 'uri' => 'file:///foo.rb' + } + ] + } + }) changed.process expect(changed.error).not_to be_nil end diff --git a/spec/language_server/message_spec.rb b/spec/language_server/message_spec.rb index 9a8c26328..504e66d07 100644 --- a/spec/language_server/message_spec.rb +++ b/spec/language_server/message_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Message do - it "returns MethodNotFound for unregistered methods" do - msg = Solargraph::LanguageServer::Message.select 'notARealMethod' + it 'returns MethodNotFound for unregistered methods' do + msg = described_class.select 'notARealMethod' expect(msg).to be(Solargraph::LanguageServer::Message::MethodNotFound) end - it "returns MethodNotImplemented for unregistered $ methods" do - msg = Solargraph::LanguageServer::Message.select '$/notARealMethod' + it 'returns MethodNotImplemented for unregistered $ methods' do + msg = described_class.select '$/notARealMethod' expect(msg).to be(Solargraph::LanguageServer::Message::MethodNotImplemented) end end diff --git a/spec/language_server/protocol_spec.rb b/spec/language_server/protocol_spec.rb index e88fb9c05..25764e6eb 100644 --- a/spec/language_server/protocol_spec.rb +++ b/spec/language_server/protocol_spec.rb @@ -1,4 +1,4 @@ -require 'thread' +# frozen_string_literal: true class Protocol attr_reader :response @@ -36,23 +36,24 @@ def stop describe Protocol do before :all do - @protocol = Protocol.new(Solargraph::LanguageServer::Host.new) + @protocol = described_class.new(Solargraph::LanguageServer::Host.new) end after :all do @protocol.stop end - before :each do - version = double(:GemVersion, version: Gem::Version.new('1.0.0')) - Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = double(:fetcher, search_for_dependency: [version]) + before do + version = instance_double(Gem::Version, version: Gem::Version.new('1.0.0')) + Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = + instance_double(Gem::SpecFetcher, search_for_dependency: [version]) end - after :each do + after do Solargraph::LanguageServer::Message::Extended::CheckGemVersion.fetcher = nil end - it "handles initialize" do + it 'handles initialize' do @protocol.request 'initialize', { 'capabilities' => { 'textDocument' => { @@ -69,27 +70,27 @@ def stop expect(response['result'].keys).to include('capabilities') end - it "is not stopped after initialization" do + it 'is not stopped after initialization' do expect(@protocol.host.stopped?).to be(false) end - it "configured dynamic registration capabilities from initialize" do + it 'configured dynamic registration capabilities from initialize' do expect(@protocol.host.can_register?('textDocument/completion')).to be(true) expect(@protocol.host.can_register?('textDocument/hover')).to be(false) expect(@protocol.host.can_register?('workspace/symbol')).to be(false) end - it "handles initialized" do + it 'handles initialized' do @protocol.request 'initialized', nil response = @protocol.response expect(response['error']).to be_nil end - it "configured default dynamic registration capabilities from initialized" do + it 'configured default dynamic registration capabilities from initialized' do expect(@protocol.host.registered?('textDocument/completion')).to be(true) end - it "handles textDocument/didOpen" do + it 'handles textDocument/didOpen' do @protocol.request 'textDocument/didOpen', { 'textDocument' => { 'uri' => 'file:///file.rb', @@ -107,7 +108,7 @@ def bar baz 'version' => 0 } } - response = @protocol.response + @protocol.response expect(@protocol.host.open?('file:///file.rb')).to be(true) end @@ -126,7 +127,7 @@ def bar baz expect(response['result'].length).to eq(2) end - it "handles textDocument/didChange" do + it 'handles textDocument/didChange' do @protocol.request 'textDocument/didChange', { 'textDocument' => { 'uri' => 'file:///file.rb', @@ -149,10 +150,10 @@ def bar baz ] } response = @protocol.response - # @todo What to expect? + expect(response).not_to be_nil end - it "handles textDocument/completion" do + it 'handles textDocument/completion' do @protocol.request 'textDocument/completion', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -164,13 +165,13 @@ def bar baz } response = @protocol.response expect(response['error']).to be_nil - expect(response['result']['items'].length > 0).to be(true) + expect(!response['result']['items'].empty?).to be(true) end - it "handles completionItem/resolve" do + it 'handles completionItem/resolve' do # Reuse the response from textDocument/completion response = @protocol.response - item = response['result']['items'].select{|h| h['label'] == 'bar'}.first + item = response['result']['items'].select { |h| h['label'] == 'bar' }.first @protocol.request 'completionItem/resolve', item response = @protocol.response expect(response['result']['documentation']['value']).to include('bar method') @@ -189,7 +190,7 @@ def bar baz expect(@protocol.response['error']).to be_nil end - it "documents YARD pins" do + it 'documents YARD pins' do @protocol.request 'textDocument/completion', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -200,22 +201,14 @@ def bar baz } } response = @protocol.response - item = response['result']['items'].select{|i| i['data']['path'] == 'File.absolute_path'}.first + item = response['result']['items'].select { |i| i['data']['path'] == 'File.absolute_path' }.first expect(item).not_to be_nil @protocol.request 'completionItem/resolve', item response = @protocol.response expect(response['result']['documentation']).not_to be_empty end - it "handles workspace/symbol" do - @protocol.request 'workspace/symbol', { - 'query' => 'test' - } - response = @protocol.response - expect(response['error']).to be_nil - end - - it "handles textDocument/definition" do + it 'handles textDocument/definition' do sleep 0.5 # HACK: Give the Host::Sources thread time to work @protocol.request 'textDocument/definition', { 'textDocument' => { @@ -231,7 +224,7 @@ def bar baz expect(response['result']).not_to be_empty end - it "handles textDocument/definition on undefined symbols" do + it 'handles textDocument/definition on undefined symbols' do @protocol.request 'textDocument/definition', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -246,7 +239,7 @@ def bar baz expect(response['result']).to be_empty end - it "handles textDocument/documentSymbol" do + it 'handles textDocument/documentSymbol' do @protocol.request 'textDocument/documentSymbol', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -256,7 +249,7 @@ def bar baz expect(response['error']).to be_nil end - it "handles textDocument/hover" do + it 'handles textDocument/hover' do @protocol.request 'textDocument/hover', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -285,7 +278,7 @@ def bar baz expect(@protocol.response['error']).to be_nil end - it "handles textDocument/signatureHelp" do + it 'handles textDocument/signatureHelp' do @protocol.request 'textDocument/signatureHelp', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -300,7 +293,7 @@ def bar baz expect(response['result']['signatures']).not_to be_empty end - it "handles workspace/symbol" do + it 'handles workspace/symbol' do @protocol.request 'workspace/symbol', { 'query' => 'Foo' } @@ -309,7 +302,7 @@ def bar baz expect(response['result']).not_to be_empty end - it "handles textDocument/references for namespaces" do + it 'handles textDocument/references for namespaces' do @protocol.request 'textDocument/references', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -324,7 +317,7 @@ def bar baz expect(response['result']).not_to be_empty end - it "handles textDocument/references for methods" do + it 'handles textDocument/references for methods' do @protocol.request 'textDocument/references', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -339,7 +332,7 @@ def bar baz expect(response['result']).not_to be_empty end - it "handles textDocument/rename" do + it 'handles textDocument/rename' do @protocol.request 'textDocument/rename', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -355,7 +348,7 @@ def bar baz expect(response['result']['changes']['file:///file.rb']).to be_a(Array) end - it "handles textDocument/prepareRename" do + it 'handles textDocument/prepareRename' do @protocol.request 'textDocument/prepareRename', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -371,7 +364,7 @@ def bar baz expect(response['result']).to be_a(Hash) end - it "handles textDocument/foldingRange" do + it 'handles textDocument/foldingRange' do @protocol.request 'textDocument/foldingRange', { 'textDocument' => { 'uri' => 'file:///file.rb' @@ -382,17 +375,17 @@ def bar baz expect(response['result'].length).not_to be_zero end - it "handles textDocument/didClose" do + it 'handles textDocument/didClose' do @protocol.request 'textDocument/didClose', { 'textDocument' => { 'uri' => 'file:///file.rb' } } - response = @protocol.response + @protocol.response expect(@protocol.host.open?('file:///file.rb')).to be(false) end - it "handles $/solargraph/search" do + it 'handles $/solargraph/search' do @protocol.request '$/solargraph/search', { 'query' => 'Foo#bar' } @@ -401,7 +394,7 @@ def bar baz expect(response['result']['content']).not_to be_empty end - it "handles $/solargraph/document" do + it 'handles $/solargraph/document' do @protocol.request '$/solargraph/document', { 'query' => 'String' } @@ -410,7 +403,7 @@ def bar baz expect(response['result']['content']).not_to be_empty end - it "handles workspace/didChangeConfiguration" do + it 'handles workspace/didChangeConfiguration' do @protocol.request 'workspace/didChangeConfiguration', { 'settings' => { 'solargraph' => { @@ -423,7 +416,7 @@ def bar baz expect(@protocol.host.registered?('textDocument/completion')).to be(false) end - it "handles $/solargraph/checkGemVersion" do + it 'handles $/solargraph/checkGemVersion' do @protocol.request '$/solargraph/checkGemVersion', { verbose: false } response = @protocol.response expect(response['error']).to be_nil @@ -431,13 +424,13 @@ def bar baz expect(response['result']['available']).to be_a(String) end - it "handles $/solargraph/documentGems" do + it 'handles $/solargraph/documentGems' do @protocol.request '$/solargraph/documentGems', {} response = @protocol.response expect(response['error']).to be_nil end - it "handles textDocument/formatting" do + it 'handles textDocument/formatting' do @protocol.request 'textDocument/didOpen', { 'textDocument' => { 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.realpath('spec/fixtures/formattable.rb')), @@ -455,7 +448,7 @@ def bar baz expect(response['result'].first['newText']).to be_a(String) end - it "can format file without file extension" do + it 'can format file without file extension' do @protocol.request 'textDocument/didOpen', { 'textDocument' => { 'uri' => Solargraph::LanguageServer::UriHelpers.file_to_uri(File.realpath('spec/fixtures/formattable')), @@ -474,13 +467,13 @@ def bar baz # expect(response['result'].first['newText']).to include('def barbaz(parameter); end') end - it "handles MethodNotFound errors" do + it 'handles MethodNotFound errors' do @protocol.request 'notamethod', {} response = @protocol.response expect(response['error']['code']).to be(Solargraph::LanguageServer::ErrorCodes::METHOD_NOT_FOUND) end - it "handles didChangeWatchedFiles for created files" do + it 'handles didChangeWatchedFiles for created files' do @protocol.request 'workspace/didChangeWatchedFiles', { 'changes' => [ { @@ -493,7 +486,7 @@ def bar baz expect(response['error']).to be_nil end - it "handles didChangeWatchedFiles for changed files" do + it 'handles didChangeWatchedFiles for changed files' do @protocol.request 'workspace/didChangeWatchedFiles', { 'changes' => [ { @@ -506,7 +499,7 @@ def bar baz expect(response['error']).to be_nil end - it "handles didChangeWatchedFiles for deleted files" do + it 'handles didChangeWatchedFiles for deleted files' do @protocol.request 'workspace/didChangeWatchedFiles', { 'changes' => [ { @@ -519,11 +512,11 @@ def bar baz expect(response['error']).to be_nil end - it "handles didChangeWatchedFiles for invalid change types" do + it 'handles didChangeWatchedFiles for invalid change types' do @protocol.request 'workspace/didChangeWatchedFiles', { 'changes' => [ { - 'type' => -99999, + 'type' => -99_999, 'uri' => 'file:///watched-file.rb' } ] @@ -532,7 +525,7 @@ def bar baz expect(response['error']).not_to be_nil end - it "adds folders to the workspace" do + it 'adds folders to the workspace' do dir = File.absolute_path('spec/fixtures/workspace_folders/folder1') uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) @protocol.request 'workspace/didChangeWorkspaceFolders', { @@ -549,7 +542,7 @@ def bar baz expect(@protocol.host.folders).to include(dir) end - it "removes folders from the workspace" do + it 'removes folders from the workspace' do dir = File.absolute_path('spec/fixtures/workspace_folders/folder1') uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(dir) @protocol.request 'workspace/didChangeWorkspaceFolders', { @@ -566,27 +559,27 @@ def bar baz expect(@protocol.host.folders).not_to include(dir) end - it "handles $/cancelRequest" do - expect { + it 'handles $/cancelRequest' do + expect do @protocol.request '$/cancelRequest', { 'id' => 0 } - }.not_to raise_error + end.not_to raise_error end - it "handles $/solargraph/environment" do + it 'handles $/solargraph/environment' do @protocol.request '$/solargraph/environment', {} response = @protocol.response expect(response['result']['content']).not_to be_nil end - it "handles shutdown" do + it 'handles shutdown' do @protocol.request 'shutdown', {} response = @protocol.response expect(response['error']).to be_nil end - it "handles exit" do + it 'handles exit' do @protocol.request 'exit', {} response = @protocol.response expect(response['error']).to be_nil diff --git a/spec/language_server/transport/adapter_spec.rb b/spec/language_server/transport/adapter_spec.rb index 3d30dd4e6..23d6ac123 100644 --- a/spec/language_server/transport/adapter_spec.rb +++ b/spec/language_server/transport/adapter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + class AdapterTester include Solargraph::LanguageServer::Transport::Adapter @@ -15,21 +17,21 @@ def flush end describe Solargraph::LanguageServer::Transport::Adapter do - it "creates a host on open" do + it 'creates a host on open' do tester = AdapterTester.new tester.opening expect(tester.host).to be_a(Solargraph::LanguageServer::Host) expect(tester.host).not_to be_stopped end - it "stops a host on close" do + it 'stops a host on close' do tester = AdapterTester.new tester.opening tester.closing expect(tester.host).to be_stopped end - it "stops Backport when the host stops" do + it 'stops Backport when the host stops' do tester = AdapterTester.new Backport.run do tester.opening @@ -40,13 +42,13 @@ def flush expect(tester.host).to be_stopped end - it "processes sent data" do + it 'processes sent data' do tester = AdapterTester.new tester.opening message = '{"jsonrpc": "2.0", "id": 1, "method": "initialize", "params": {}}' - expect { + expect do tester.receiving "Content-Length: #{message.length}\r\n\r\n#{message}" - }.not_to raise_error + end.not_to raise_error tester.closing end end diff --git a/spec/language_server/transport/data_reader_spec.rb b/spec/language_server/transport/data_reader_spec.rb index c035d2b2c..179f14a24 100644 --- a/spec/language_server/transport/data_reader_spec.rb +++ b/spec/language_server/transport/data_reader_spec.rb @@ -1,8 +1,10 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::Transport::DataReader do - it "rescues exceptions for invalid JSON" do - reader = Solargraph::LanguageServer::Transport::DataReader.new + it 'rescues exceptions for invalid JSON' do + reader = described_class.new handled = 0 - reader.set_message_handler do |msg| + reader.set_message_handler do |_msg| handled += 1 end msg = { @@ -10,9 +12,9 @@ method: 'test' }.to_json msg += '}' - expect { + expect do reader.receive "Content-Length:#{msg.bytesize}\r\n\r\n#{msg}" - }.not_to raise_error + end.not_to raise_error expect(handled).to eq(0) end end diff --git a/spec/language_server/uri_helpers_spec.rb b/spec/language_server/uri_helpers_spec.rb index 002a10d1c..038dca0bc 100644 --- a/spec/language_server/uri_helpers_spec.rb +++ b/spec/language_server/uri_helpers_spec.rb @@ -1,37 +1,39 @@ +# frozen_string_literal: true + describe Solargraph::LanguageServer::UriHelpers do it "doesn't escapes colons in file paths" do file = 'c:/one/two' - uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) + uri = described_class.file_to_uri(file) expect(uri).to start_with('file:///c:') end it 'uses %20 for spaces' do file = '/path/to/a file' - uri = Solargraph::LanguageServer::UriHelpers.file_to_uri(file) + uri = described_class.file_to_uri(file) expect(uri).to end_with('a%20file') end it 'removes file:// prefix' do uri = 'file:///dev_tools/' - file = Solargraph::LanguageServer::UriHelpers.uri_to_file(uri) + file = described_class.uri_to_file(uri) expect(file).to eq('/dev_tools/') end it 'removes file: prefix' do uri = 'file:/dev_tools/' - file = Solargraph::LanguageServer::UriHelpers.uri_to_file(uri) + file = described_class.uri_to_file(uri) expect(file).to eq('/dev_tools/') end it 'removes file:/// prefix when a drive is specified' do uri = 'file:///Z:/dev_tools/' - file = Solargraph::LanguageServer::UriHelpers.uri_to_file(uri) + file = described_class.uri_to_file(uri) expect(file).to eq('Z:/dev_tools/') end it 'removes file:/ prefix when a drive is specified' do uri = 'file:/Z:/dev_tools/' - file = Solargraph::LanguageServer::UriHelpers.uri_to_file(uri) + file = described_class.uri_to_file(uri) expect(file).to eq('Z:/dev_tools/') end end diff --git a/spec/library_spec.rb b/spec/library_spec.rb index f7daafdf4..9f9ab87dc 100644 --- a/spec/library_spec.rb +++ b/spec/library_spec.rb @@ -1,22 +1,24 @@ +# frozen_string_literal: true + require 'tmpdir' require 'yard' describe Solargraph::Library do - it "does not open created files in the workspace" do + it 'does not open created files in the workspace' do Dir.mktmpdir do |temp_dir_path| # Ensure we resolve any symlinks to their real path workspace_path = File.realpath(temp_dir_path) file_path = File.join(workspace_path, 'file.rb') File.write(file_path, 'a = b') - library = Solargraph::Library.load(workspace_path) + library = described_class.load(workspace_path) result = library.create(file_path, File.read(file_path)) expect(result).to be(true) expect(library.open?(file_path)).to be(false) end end - it "returns a Completion" do - library = Solargraph::Library.new + it 'returns a Completion' do + library = described_class.new library.attach Solargraph::Source.load_string(%( x = 1 x @@ -31,9 +33,9 @@ Solargraph::Shell.new.uncache('backport') end - it "returns a Completion", time_limit_seconds: 50 do - library = Solargraph::Library.new(Solargraph::Workspace.new(Dir.pwd, - Solargraph::Workspace::Config.new)) + it 'returns a Completion', time_limit_seconds: 50 do + library = described_class.new(Solargraph::Workspace.new(Dir.pwd, + Solargraph::Workspace::Config.new)) library.attach Solargraph::Source.load_string(%( require 'backport' @@ -42,7 +44,6 @@ def foo(adapter) adapter.remo end ), 'file.rb', 0) - completion = nil # give Solargraph time to cache the gem while (completion = library.completions_at('file.rb', 5, 19)).pins.empty? sleep 0.25 @@ -57,9 +58,9 @@ def foo(adapter) Solargraph::Shell.new.gems('backport') end - it "returns a Completion" do - library = Solargraph::Library.new(Solargraph::Workspace.new(Dir.pwd, - Solargraph::Workspace::Config.new)) + it 'returns a Completion' do + library = described_class.new(Solargraph::Workspace.new(Dir.pwd, + Solargraph::Workspace::Config.new)) library.attach Solargraph::Source.load_string(%( require 'backport' @@ -74,8 +75,8 @@ def foo(adapter) end end - it "gets definitions from a file" do - library = Solargraph::Library.new + it 'gets definitions from a file' do + library = described_class.new src = Solargraph::Source.load_string %( class Foo def bar @@ -87,8 +88,8 @@ def bar expect(paths).to include('Foo#bar') end - it "gets type definitions from a file" do - library = Solargraph::Library.new + it 'gets type definitions from a file' do + library = described_class.new src = Solargraph::Source.load_string %( class Bar; end class Foo @@ -103,8 +104,8 @@ def self.bar expect(paths).to include('Bar') end - it "signifies method arguments" do - library = Solargraph::Library.new + it 'signifies method arguments' do + library = described_class.new src = Solargraph::Source.load_string %( class Foo def bar baz, key: '' @@ -118,18 +119,18 @@ def bar baz, key: '' expect(pins.first.path).to eq('Foo#bar') end - it "ignores invalid filenames in create_from_disk" do - library = Solargraph::Library.new + it 'ignores invalid filenames in create_from_disk' do + library = described_class.new filename = 'not_a_real_file.rb' expect(library.create_from_disk(filename)).to be(false) expect(library.contain?(filename)).to be(false) end - it "adds mergeable files to the workspace in create_from_disk" do + it 'adds mergeable files to the workspace in create_from_disk' do Dir.mktmpdir do |temp_dir_path| # Ensure we resolve any symlinks to their real path workspace_path = File.realpath(temp_dir_path) - library = Solargraph::Library.load(workspace_path) + library = described_class.load(workspace_path) file_path = File.join(workspace_path, 'created.rb') File.write(file_path, "puts 'hello'") expect(library.create_from_disk(file_path)).to be(true) @@ -137,9 +138,9 @@ def bar baz, key: '' end end - it "ignores non-mergeable files in create_from_disk" do + it 'ignores non-mergeable files in create_from_disk' do Dir.mktmpdir do |dir| - library = Solargraph::Library.load(dir) + library = described_class.load(dir) filename = File.join(dir, 'created.txt') File.write(filename, "puts 'hello'") expect(library.create_from_disk(filename)).to be(false) @@ -147,8 +148,8 @@ def bar baz, key: '' end end - it "diagnoses files" do - library = Solargraph::Library.new + it 'diagnoses files' do + library = described_class.new src = Solargraph::Source.load_string(%( puts 'hello' ), 'file.rb', 0) @@ -163,7 +164,7 @@ def bar baz, key: '' config = instance_double(Solargraph::Workspace::Config) allow(config).to receive_messages(plugins: [], required: [], reporters: ['all!']) workspace = Solargraph::Workspace.new directory, config - library = Solargraph::Library.new workspace + library = described_class.new workspace src = Solargraph::Source.load_string(%( puts 'hello' ), 'file.rb', 0) @@ -172,8 +173,8 @@ def bar baz, key: '' expect(result.to_s).to include('rubocop') end - it "documents symbols" do - library = Solargraph::Library.new + it 'documents symbols' do + library = described_class.new src = Solargraph::Source.load_string(%( class Foo def bar @@ -188,9 +189,9 @@ def bar end describe '#references_from' do - it "collects references to a new method on a constant from assignment of Class.new" do + it 'collects references to a new method on a constant from assignment of Class.new' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( Foo.new ), 'file1.rb', 0) @@ -202,12 +203,12 @@ def bar library.catalog locs = library.references_from('file1.rb', 1, 12) expect(locs.map { |l| [l.filename, l.range.start.line] }) - .to eq([["file1.rb", 1]]) + .to eq([['file1.rb', 1]]) end - it "collects references to a new method to a constant from assignment" do + it 'collects references to a new method to a constant from assignment' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( Foo.new ), 'file1.rb', 0) @@ -221,12 +222,12 @@ class Foo library.catalog locs = library.references_from('file2.rb', 3, 21) expect(locs.map { |l| [l.filename, l.range.start.line] }) - .to eq([["file1.rb", 1], ["file2.rb", 3]]) + .to eq([['file1.rb', 1], ['file2.rb', 3]]) end - it "collects references to an instance method symbol" do + it 'collects references to an instance method symbol' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( class Foo def bar @@ -248,12 +249,12 @@ def bar; end library.catalog locs = library.references_from('file2.rb', 2, 11) expect(locs.length).to eq(3) - expect(locs.select{|l| l.filename == 'file2.rb' && l.range.start.line == 6}).to be_empty + expect(locs.select { |l| l.filename == 'file2.rb' && l.range.start.line == 6 }).to be_empty end - it "collects references to a class method symbol" do + it 'collects references to a class method symbol' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( class Foo def self.bar @@ -281,14 +282,14 @@ def bar; end library.catalog locs = library.references_from('file2.rb', 1, 11) expect(locs.length).to eq(3) - expect(locs.select{|l| l.filename == 'file1.rb' && l.range.start.line == 2}).not_to be_empty - expect(locs.select{|l| l.filename == 'file1.rb' && l.range.start.line == 9}).not_to be_empty - expect(locs.select{|l| l.filename == 'file2.rb' && l.range.start.line == 1}).not_to be_empty + expect(locs.select { |l| l.filename == 'file1.rb' && l.range.start.line == 2 }).not_to be_empty + expect(locs.select { |l| l.filename == 'file1.rb' && l.range.start.line == 9 }).not_to be_empty + expect(locs.select { |l| l.filename == 'file2.rb' && l.range.start.line == 1 }).not_to be_empty end - it "collects stripped references to constant symbols" do + it 'collects stripped references to constant symbols' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) src1 = Solargraph::Source.load_string(%( class Foo def bar @@ -311,13 +312,13 @@ class Other code = library.read_text(l.filename) o1 = Solargraph::Position.to_offset(code, l.range.start) o2 = Solargraph::Position.to_offset(code, l.range.ending) - expect(code[o1..o2-1]).to eq('Foo') + expect(code[o1..(o2 - 1)]).to eq('Foo') end end it 'rejects new references from different classes' do workspace = Solargraph::Workspace.new('*') - library = Solargraph::Library.new(workspace) + library = described_class.new(workspace) source = Solargraph::Source.load_string(%( class Foo def bar @@ -334,21 +335,21 @@ def bar expect(obj_new_locs).to eq([Solargraph::Location.new('test.rb', Solargraph::Range.from_to(6, 12, 6, 15))]) end - it "searches the core for queries" do - library = Solargraph::Library.new + it 'searches the core for queries' do + library = described_class.new result = library.search('String') expect(result).not_to be_empty end - it "returns YARD documentation from the core" do - library = Solargraph::Library.new - api_map, result = library.document('String') + it 'returns YARD documentation from the core' do + library = described_class.new + _, result = library.document('String') expect(result).not_to be_empty expect(result.first).to be_a(Solargraph::Pin::Base) end - it "returns YARD documentation from sources" do - library = Solargraph::Library.new + it 'returns YARD documentation from sources' do + library = described_class.new src = Solargraph::Source.load_string(%( class Foo # My bar method @@ -356,13 +357,13 @@ def bar; end end ), 'test.rb', 0) library.attach src - api_map, result = library.document('Foo#bar') + _, result = library.document('Foo#bar') expect(result).not_to be_empty expect(result.first).to be_a(Solargraph::Pin::Base) end - it "synchronizes sources from updaters" do - library = Solargraph::Library.new + it 'synchronizes sources from updaters' do + library = described_class.new src = Solargraph::Source.load_string(%( class Foo end @@ -382,8 +383,8 @@ def bar; end expect(library.current.code).to eq(repl) end - it "finds unique references" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + it 'finds unique references' do + library = described_class.new(Solargraph::Workspace.new('*')) src1 = Solargraph::Source.load_string(%( class Foo end @@ -398,8 +399,8 @@ class Foo expect(refs.length).to eq(2) end - it "includes method parameters in references" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + it 'includes method parameters in references' do + library = described_class.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( class Foo def bar(baz) @@ -415,7 +416,7 @@ def bar(baz) end it "lies about names when client can't handle the truth" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + library = described_class.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( class Foo def 🤦🏻foo♀️; 123; end @@ -426,8 +427,8 @@ def 🤦🏻foo♀️; 123; end expect(from_def.first.range.start.column).to eq(14) end - it "tells the truth about names when client can handle the truth" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + it 'tells the truth about names when client can handle the truth' do + library = described_class.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( class Foo def 🤦🏻foo♀️; 123; end @@ -438,8 +439,8 @@ def 🤦🏻foo♀️; 123; end expect(from_def.first.range.start.column).to eq(12) end - it "includes block parameters in references" do - library = Solargraph::Library.new(Solargraph::Workspace.new('*')) + it 'includes block parameters in references' do + library = described_class.new(Solargraph::Workspace.new('*')) source = Solargraph::Source.load_string(%( 100.times do |foo| puts foo @@ -454,7 +455,7 @@ def 🤦🏻foo♀️; 123; end end it 'defines YARD tags' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class TaggedExample end @@ -476,7 +477,7 @@ def foo; end end it 'defines YARD tags with nested namespaces' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class Tagged class Example; end @@ -496,7 +497,7 @@ def foo; end end it 'defines generic YARD tags' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class TaggedExample; end class CallerExample @@ -510,7 +511,7 @@ def foo; end end it 'defines multiple YARD tags' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class TaggedExample; end class CallerExample @@ -524,7 +525,7 @@ def foo; end end it 'skips comment text outside of tags' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( # String def foo; end @@ -535,7 +536,7 @@ def foo; end end it 'marks aliases as methods or attributes in completion items' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class Example attr_reader :foo @@ -558,7 +559,7 @@ def baz end it 'marks aliases as methods or attributes in definitions' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class Example attr_reader :foo @@ -577,7 +578,7 @@ def bar; end end it 'detaches current source with nil' do - library = Solargraph::Library.new + library = described_class.new source = Solargraph::Source.load_string(%( class Example attr_reader :foo @@ -595,7 +596,7 @@ def bar; end describe '#locate_ref' do it 'returns nil without a matching reference location' do workspace = File.absolute_path(File.join('spec', 'fixtures', 'workspace')) - library = Solargraph::Library.load(workspace) + library = described_class.load(workspace) library.map! location = Solargraph::Location.new(File.join(workspace, 'app.rb'), Solargraph::Range.from_to(0, 8, 0, 8)) found = library.locate_ref(location) @@ -606,7 +607,7 @@ def bar; end describe '#delete' do it 'removes files from Library#source_map_hash' do workspace = File.absolute_path(File.join('spec', 'fixtures', 'workspace')) - library = Solargraph::Library.load(workspace) + library = described_class.load(workspace) library.map! library.catalog other_file = File.absolute_path(File.join('spec', 'fixtures', 'workspace', 'lib', 'other.rb')) @@ -623,36 +624,36 @@ def bar; end end end - context 'unsynchronized' do - let(:library) { Solargraph::Library.load File.absolute_path(File.join('spec', 'fixtures', 'workspace')) } + context 'when unsynchronized' do + let(:library) { described_class.load File.absolute_path(File.join('spec', 'fixtures', 'workspace')) } let(:good_file) { File.join(library.workspace.directory, 'lib', 'thing.rb') } let(:bad_file) { File.join(library.workspace.directory, 'lib', 'not_a_thing.rb') } describe 'Library#completions_at' do it 'gracefully handles unmapped sources' do - expect { + expect do library.completions_at(good_file, 0, 0) - }.not_to raise_error + end.not_to raise_error end it 'raises errors for nonexistent sources' do - expect { + expect do library.completions_at(bad_file, 0, 0) - }.to raise_error(Solargraph::FileNotFoundError) + end.to raise_error(Solargraph::FileNotFoundError) end end describe 'Library#definitions_at' do it 'gracefully handles unmapped sources' do - expect { + expect do library.definitions_at(good_file, 0, 0) - }.not_to raise_error + end.not_to raise_error end it 'raises errors for nonexistent sources' do - expect { + expect do library.definitions_at(bad_file, 0, 0) - }.to raise_error(Solargraph::FileNotFoundError) + end.to raise_error(Solargraph::FileNotFoundError) end end end diff --git a/spec/logging_spec.rb b/spec/logging_spec.rb index eee59e606..7dcd52000 100644 --- a/spec/logging_spec.rb +++ b/spec/logging_spec.rb @@ -1,15 +1,17 @@ +# frozen_string_literal: true + require 'tempfile' describe Solargraph::Logging do - it "logs messages with levels" do + it 'logs messages with levels' do file = Tempfile.new('log') - Solargraph::Logging.logger.reopen file - Solargraph::Logging.logger.warn "Test" + described_class.logger.reopen file + described_class.logger.warn 'Test' file.rewind msg = file.read file.close file.unlink - Solargraph::Logging.logger.reopen STDERR + described_class.logger.reopen File::NULL expect(msg).to include('WARN') end end diff --git a/spec/parser/flow_sensitive_typing_spec.rb b/spec/parser/flow_sensitive_typing_spec.rb index 10c9c0849..cee6afef1 100644 --- a/spec/parser/flow_sensitive_typing_spec.rb +++ b/spec/parser/flow_sensitive_typing_spec.rb @@ -979,7 +979,6 @@ def foo a expect(clip.infer.to_s).to eq('Integer') end - it 'supports !@x.nil && @x.y' do source = Solargraph::Source.load_string(%( class Bar diff --git a/spec/parser/node_chainer_spec.rb b/spec/parser/node_chainer_spec.rb index 5f3865b13..8f24eb335 100644 --- a/spec/parser/node_chainer_spec.rb +++ b/spec/parser/node_chainer_spec.rb @@ -1,33 +1,35 @@ +# frozen_string_literal: true + describe 'NodeChainer' do def chain_string str Solargraph::Parser.chain_string(str, 'file.rb', 0) end - it "recognizes self keywords" do + it 'recognizes self keywords' do chain = chain_string('self.foo') expect(chain.links.first.word).to eq('self') expect(chain.links.first).to be_a(Solargraph::Source::Chain::Head) end - it "recognizes super keywords" do + it 'recognizes super keywords' do chain = chain_string('super.foo') expect(chain.links.first.word).to eq('super') expect(chain.links.first).to be_a(Solargraph::Source::Chain::ZSuper) end - it "recognizes constants" do + it 'recognizes constants' do chain = chain_string('Foo::Bar') expect(chain.links.length).to eq(1) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Constant) expect(chain.links.map(&:word)).to eq(['Foo::Bar']) end - it "splits method calls with arguments and blocks" do + it 'splits method calls with arguments and blocks' do chain = chain_string('var.meth1(1, 2).meth2 do; end') - expect(chain.links.map(&:word)).to eq(['var', 'meth1', 'meth2']) + expect(chain.links.map(&:word)).to eq(%w[var meth1 meth2]) end - it "recognizes literals" do + it 'recognizes literals' do chain = chain_string('"string"') expect(chain).to be_literal chain = chain_string('100') @@ -38,22 +40,22 @@ def chain_string str expect(chain).to be_literal end - it "recognizes instance variables" do + it 'recognizes instance variables' do chain = chain_string('@foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::InstanceVariable) end - it "recognizes class variables" do + it 'recognizes class variables' do chain = chain_string('@@foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::ClassVariable) end - it "recognizes global variables" do + it 'recognizes global variables' do chain = chain_string('$foo') expect(chain.links.first).to be_a(Solargraph::Source::Chain::GlobalVariable) end - it "operates on nodes" do + it 'operates on nodes' do source = Solargraph::Source.load_string(%( class Foo Bar.meth1(1, 2).meth2{} @@ -61,7 +63,7 @@ class Foo )) node = source.node_at(2, 26) chain = Solargraph::Parser.chain(node) - expect(chain.links.map(&:word)).to eq(['Bar', 'meth1', 'meth2']) + expect(chain.links.map(&:word)).to eq(%w[Bar meth1 meth2]) end it 'chains and/or nodes' do diff --git a/spec/parser/node_methods_spec.rb b/spec/parser/node_methods_spec.rb index 867180bc6..1ead0a6b6 100644 --- a/spec/parser/node_methods_spec.rb +++ b/spec/parser/node_methods_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # These tests are deliberately generic because they apply to both the Legacy # and Rubyvm node methods. describe Solargraph::Parser::NodeMethods do @@ -5,90 +7,92 @@ def parse source Solargraph::Parser.parse(source, 'test.rb', 0) end - it "unpacks constant nodes into strings" do - ast = parse("Foo::Bar") - expect(Solargraph::Parser::NodeMethods.unpack_name(ast)).to eq "Foo::Bar" + it 'unpacks constant nodes into strings' do + ast = parse('Foo::Bar') + expect(described_class.unpack_name(ast)).to eq 'Foo::Bar' end - it "infers literal strings" do + it 'infers literal strings' do ast = parse("x = 'string'") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::String' + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::String' end - it "infers literal hashes" do - ast = parse("x = {}") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Hash' + it 'infers literal hashes' do + ast = parse('x = {}') + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::Hash' end - it "infers literal arrays" do - ast = parse("x = []") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Array' + it 'infers literal arrays' do + ast = parse('x = []') + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::Array' end - it "infers literal integers" do - ast = parse("x = 100") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Integer' + it 'infers literal integers' do + ast = parse('x = 100') + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::Integer' end - it "infers literal floats" do - ast = parse("x = 10.1") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast.children[1])).to eq '::Float' + it 'infers literal floats' do + ast = parse('x = 10.1') + expect(described_class.infer_literal_node_type(ast.children[1])).to eq '::Float' end - it "infers literal symbols" do - ast = parse(":symbol") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' + it 'infers literal symbols' do + ast = parse(':symbol') + expect(described_class.infer_literal_node_type(ast)).to eq '::Symbol' end - it "infers double quoted symbols" do + it 'infers double quoted symbols' do ast = parse(':"symbol"') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' + expect(described_class.infer_literal_node_type(ast)).to eq '::Symbol' end - it "infers interpolated double quoted symbols" do - ast = parse(':"#{Object}"') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' + it 'infers interpolated double quoted symbols' do + ast = parse(%(:"#{Object}")) + expect(described_class.infer_literal_node_type(ast)).to eq '::Symbol' end - it "infers single quoted symbols" do + it 'infers single quoted symbols' do ast = parse(":'symbol'") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(ast)).to eq '::Symbol' + expect(described_class.infer_literal_node_type(ast)).to eq '::Symbol' end it 'infers literal booleans' do - true_ast = parse("true") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(true_ast)).to eq '::Boolean' - false_ast = parse("false") - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(false_ast)).to eq '::Boolean' + true_ast = parse('true') + expect(described_class.infer_literal_node_type(true_ast)).to eq '::Boolean' + false_ast = parse('false') + expect(described_class.infer_literal_node_type(false_ast)).to eq '::Boolean' end - it "handles return nodes with implicit nil values" do + it 'handles empty return nodes with implicit nil values' do node = parse(%( return if true )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) # @todo Should there be two returns, the second being nil? expect(rets.map(&:to_s)).to eq(['(nil)', '(nil)']) + # The expectation is changing from previous versions. If conditions + # have an implicit else branch, so this node should return [nil, nil]. expect(rets.length).to eq(2) end - it "handles return nodes with implicit nil values" do + it 'handles local return nodes with implicit nil values' do node = parse(%( return bla if true )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) # Two returns, the second being implicit nil expect(rets.length).to eq(2) end - it 'handles return nodes from case statements' do + it 'handles boolean return nodes from case statements without else' do node = parse(%( case x when 100 true end )) - returns = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + returns = described_class.returns_from_method_body(node) # Include an implicit `nil` for missing else expect(returns.map(&:to_s)).to eq(['(true)', '(nil)']) end @@ -112,7 +116,7 @@ def parse source end end )) - returns = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + returns = described_class.returns_from_method_body(node) expect(returns.length).to eq(6) expect(returns.map(&:to_s)).to eq(['(true)', '(int 73)', '(false)', '(nil)', '(false)', '(true)']) end @@ -126,87 +130,45 @@ def parse source false end )) - returns = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + returns = described_class.returns_from_method_body(node) expect(returns.length).to eq(2) end - it "handles return nodes in reduceable (begin) nodes" do - # @todo Temporarily disabled. Result is 3 nodes instead of 2. - # node = parse(%( - # begin - # return if true - # end - # )) - # rets = Solargraph::Parser::NodeMethods.returns_from(node) - # expect(rets.length).to eq(2) - end + it 'handles return nodes in reduceable (begin) nodes' do + pending('Temporarily disabled. Result is 3 nodes instead of 2.') - it "handles return nodes after other nodes" do node = parse(%( - x = 1 - return x - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) - end - - it "handles return nodes with unreachable code" do - node = parse(%( - x = 1 - return x - y + begin + return if true + end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) + rets = described_class.returns_from_method_body(node) + expect(rets.length).to eq(2) end - it "handles conditional returns with following code" do + it 'handles conditional returns with following code' do node = parse(%( x = 1 return x if foo y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(2) end - it "handles return nodes with reduceable code" do - node = parse(%( - return begin - x if foo - y - end - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) - end - - it "handles top 'and' nodes" do - node = parse('1 && "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) - expect(rets[0].type.to_s.downcase).to eq('and') - end - - it "handles top 'or' nodes" do - node = parse('1 || "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.length).to eq(1) - end - it "handles nested 'and' nodes" do node = parse('return 1 && "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(1) expect(rets[0].type.to_s.downcase).to eq('and') end it "handles nested 'or' nodes" do node = parse('return 1 || "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(2) - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[0])).to eq('::Integer') - expect(Solargraph::Parser::NodeMethods.infer_literal_node_type(rets[1])).to eq('::String') + expect(described_class.infer_literal_node_type(rets[0])).to eq('::Integer') + expect(described_class.infer_literal_node_type(rets[1])).to eq('::String') end it 'finds return nodes in blocks' do @@ -215,8 +177,8 @@ def parse source return item if foo end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:block, :lvar]) + rets = described_class.returns_from_method_body(node) + expect(rets.map(&:type)).to eq(%i[block lvar]) end it 'finds correct return node line in begin expressions' do @@ -226,7 +188,7 @@ def parse source '123' end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:str]) end @@ -239,147 +201,101 @@ def parse source end nil )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:lvar, :nil]) + rets = described_class.returns_from_method_body(node) + expect(rets.map(&:type)).to eq(%i[lvar nil]) end - it "handles return nodes with implicit nil values" do - node = parse(%( - return if true - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - # The expectation is changing from previous versions. If conditions - # have an implicit else branch, so this node should return [nil, nil]. - expect(rets.length).to eq(2) - end - - it "handles return nodes with implicit nil values" do + it 'handles return nodes with implicit nil values' do node = parse(%( return bla if true )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:send, :nil]) + rets = described_class.returns_from_method_body(node) + expect(rets.map(&:type)).to eq(%i[send nil]) end - it "handles return nodes in reduceable (begin) nodes" do - # @todo Temporarily disabled. Result is 3 nodes instead of 2 in legacy. - # node = parse(%( - # begin - # return if true - # end - # )) - # rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - # expect(rets.length).to eq(2) - end - - it "handles return nodes after other nodes" do + it 'handles return nodes after other nodes' do node = parse(%( x = 1 return x )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:lvar]) end - it "handles return nodes with unreachable code" do + it 'handles return nodes with unreachable code' do node = parse(%( x = 1 return x y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(1) end - it "short-circuits return node finding after a raise statement in a begin expression" do + it 'short-circuits return node finding after a raise statement in a begin expression' do pending('case being handled') node = parse(%( raise "Error" y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(0) end - it "does not short circuit return node finding after a raise statement in a conditional" do + it 'does not short circuit return node finding after a raise statement in a conditional' do node = parse(%( x = 1 raise "Error" if foo y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(1) end - it "does not short circuit return node finding after a return statement in a conditional" do + it 'does not short circuit return node finding after a return statement in a conditional' do node = parse(%( x = 1 return "Error" if foo y )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(2) end - it "handles return nodes with reduceable code" do + it 'handles return nodes with reduceable code' do node = parse(%( return begin x if foo y end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.length).to eq(1) end it "handles top 'and' nodes" do node = parse('1 && "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:and]) end it "handles top 'or' nodes" do node = parse('1 || "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:or]) end it "handles nested 'and' nodes from return" do node = parse('return 1 && "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) + rets = described_class.returns_from_method_body(node) expect(rets.map(&:type)).to eq([:and]) end it "handles nested 'or' nodes from return" do node = parse('return 1 || "2"') - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:int, :str]) - end - - it 'finds return nodes in blocks' do - node = parse(%( - array.each do |item| - return item if foo - end - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:block, :lvar]) - # expect(rets[1].type).to eq(:DVAR) - end - - it 'returns nested return blocks' do - node = parse(%( - if foo - array.each do |item| - return item if foo - end - end - nil - )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:lvar, :nil]) - # expect(rets[0].type).to eq(:DVAR) + rets = described_class.returns_from_method_body(node) + expect(rets.map(&:type)).to eq(%i[int str]) end it 'handles return nodes from case statements' do @@ -390,19 +306,19 @@ def parse source "" end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:str, :str]) + rets = described_class.returns_from_method_body(node) + expect(rets.map(&:type)).to eq(%i[str str]) end - it 'handles return nodes from case statements without else' do + it 'handles String return nodes from case statements without else' do node = parse(%( case 1 when 1 "" end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:str, :nil]) + rets = described_class.returns_from_method_body(node) + expect(rets.map(&:type)).to eq(%i[str nil]) end it 'handles return nodes from case statements with super' do @@ -414,20 +330,20 @@ def parse source super end )) - rets = Solargraph::Parser::NodeMethods.returns_from_method_body(node) - expect(rets.map(&:type)).to eq([:send, :zsuper]) + rets = described_class.returns_from_method_body(node) + expect(rets.map(&:type)).to eq(%i[send zsuper]) end describe 'convert_hash' do it 'converts literal hash arguments' do node = parse('{foo: :bar}') - hash = Solargraph::Parser::NodeMethods.convert_hash(node) + hash = described_class.convert_hash(node) expect(hash.keys).to eq([:foo]) end it 'ignores call arguments' do node = parse('some_call') - hash = Solargraph::Parser::NodeMethods.convert_hash(node) + hash = described_class.convert_hash(node) expect(hash).to eq({}) end end @@ -441,7 +357,7 @@ def super_with_block end end )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls).to be_one end @@ -449,7 +365,7 @@ def super_with_block source = Solargraph::Source.load_string(%( Foo.new.bar('string') )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls.length).to eq(2) end @@ -457,7 +373,7 @@ def super_with_block source = Solargraph::Source.load_string(%( [ Foo.new.bar('string') ] )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls.length).to eq(2) end @@ -465,7 +381,7 @@ def super_with_block source = Solargraph::Source.load_string(%( [ Foo.new.bar('string') ].compact )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls.length).to eq(3) end @@ -481,7 +397,7 @@ def something end end )) - calls = Solargraph::Parser::NodeMethods.call_nodes_from(source.node) + calls = described_class.call_nodes_from(source.node) expect(calls.length).to eq(2) end end diff --git a/spec/parser/node_processor_spec.rb b/spec/parser/node_processor_spec.rb index 2033e21ca..da7031779 100644 --- a/spec/parser/node_processor_spec.rb +++ b/spec/parser/node_processor_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Parser::NodeProcessor do def parse source Solargraph::Parser.parse(source, 'file.rb', 0) @@ -9,16 +11,16 @@ class Foo private_constant end )) - expect { - Solargraph::Parser::NodeProcessor.process(node) - }.not_to raise_error + expect do + described_class.process(node) + end.not_to raise_error end it 'orders optional args correctly' do node = parse(%( def foo(bar = nil, baz = nil); end )) - pins, = Solargraph::Parser::NodeProcessor.process(node) + pins, = described_class.process(node) # Method pin is first pin after default namespace pin = pins[1] expect(pin.parameters.map(&:name)).to eq(%w[bar baz]) @@ -30,7 +32,7 @@ def foo(bar = nil, baz = nil); end detail += "foo" detail.strip! )) - _, vars = Solargraph::Parser::NodeProcessor.process(node) + _, vars = described_class.process(node) # ensure we parsed the += correctly and won't report an unexpected # nil assignment @@ -55,17 +57,17 @@ def process end end - Solargraph::Parser::NodeProcessor.register(:def, dummy_processor1) - Solargraph::Parser::NodeProcessor.register(:def, dummy_processor2) + described_class.register(:def, dummy_processor1) + described_class.register(:def, dummy_processor2) node = parse(%( def some_method; end )) - pins, = Solargraph::Parser::NodeProcessor.process(node) + pins, = described_class.process(node) # empty namespace pin is root namespace expect(pins.map(&:name)).to contain_exactly('', 'foo', 'bar', 'some_method') # Clean up the registered processors - Solargraph::Parser::NodeProcessor.deregister(:def, dummy_processor1) - Solargraph::Parser::NodeProcessor.deregister(:def, dummy_processor2) + described_class.deregister(:def, dummy_processor1) + described_class.deregister(:def, dummy_processor2) end end diff --git a/spec/parser_spec.rb b/spec/parser_spec.rb index 3c1e3cca0..62fe7d955 100644 --- a/spec/parser_spec.rb +++ b/spec/parser_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + describe Solargraph::Parser do def parse source Solargraph::Parser.parse(source, 'file.rb', 0) end - it "parses nodes" do + it 'parses nodes' do node = parse('class Foo; end') - expect(Solargraph::Parser.is_ast_node?(node)).to be(true) + expect(described_class.is_ast_node?(node)).to be(true) end it 'raises repairable SyntaxError for unknown encoding errors' do diff --git a/spec/pin/base_spec.rb b/spec/pin/base_spec.rb index 1a6cfd1e8..4d4315040 100644 --- a/spec/pin/base_spec.rb +++ b/spec/pin/base_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Base do let(:zero_location) { Solargraph::Location.new('test.rb', Solargraph::Range.from_to(0, 0, 0, 0)) } let(:one_location) { Solargraph::Location.new('test.rb', Solargraph::Range.from_to(0, 0, 1, 0)) } - it "will not combine pins with directive changes" do - pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A Foo class', - source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) - pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', - source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) + it 'does not combine pins with directive changes' do + pin1 = described_class.new(location: zero_location, name: 'Foo', comments: 'A Foo class', + source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) + pin2 = described_class.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', + source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) expect(pin1.nearly?(pin2)).to be(false) # enable asserts with_env_var('SOLARGRAPH_ASSERTS', 'on') do @@ -14,38 +16,38 @@ end end - it "will not combine pins with different directives" do - pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', - source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) - pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@!macro other', - source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) + it 'does not combine pins with different directives' do + pin1 = described_class.new(location: zero_location, name: 'Foo', comments: '@!macro my_macro', + source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) + pin2 = described_class.new(location: zero_location, name: 'Foo', comments: '@!macro other', + source: :yardoc, closure: Solargraph::Pin::ROOT_PIN) expect(pin1.nearly?(pin2)).to be(false) with_env_var('SOLARGRAPH_ASSERTS', 'on') do expect { pin1.combine_with(pin2) }.to raise_error(RuntimeError, /Inconsistent :macros values/) end end - it "sees tag differences as not near or equal" do - pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@return [Foo]') - pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@return [Bar]') + it 'sees tag differences as not near or equal' do + pin1 = described_class.new(location: zero_location, name: 'Foo', comments: '@return [Foo]') + pin2 = described_class.new(location: zero_location, name: 'Foo', comments: '@return [Bar]') expect(pin1.nearly?(pin2)).to be(false) expect(pin1 == pin2).to be(false) end - it "sees comment differences as nearly but not equal" do - pin1 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A Foo class') - pin2 = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: 'A different Foo') + it 'sees comment differences as nearly but not equal' do + pin1 = described_class.new(location: zero_location, name: 'Foo', comments: 'A Foo class') + pin2 = described_class.new(location: zero_location, name: 'Foo', comments: 'A different Foo') expect(pin1.nearly?(pin2)).to be(true) expect(pin1 == pin2).to be(false) end - it "recognizes deprecated tags" do - pin = Solargraph::Pin::Base.new(location: zero_location, name: 'Foo', comments: '@deprecated Use Bar instead.') + it 'recognizes deprecated tags' do + pin = described_class.new(location: zero_location, name: 'Foo', comments: '@deprecated Use Bar instead.') expect(pin).to be_deprecated end - it "does not link documentation for undefined return types" do - pin = Solargraph::Pin::Base.new(name: 'Foo', comments: '@return [undefined]') + it 'does not link documentation for undefined return types' do + pin = described_class.new(name: 'Foo', comments: '@return [undefined]') expect(pin.link_documentation).to eq('Foo') end @@ -56,8 +58,8 @@ expect(pins.length).to eq(1) parser_method_pin = pins.first return_type = parser_method_pin.typify(api_map) - expect(parser_method_pin.closure.name).to eq("Docstring") - expect(parser_method_pin.closure.gates).to eq(["YARD::Docstring", "YARD", '']) + expect(parser_method_pin.closure.name).to eq('Docstring') + expect(parser_method_pin.closure.gates).to eq(['YARD::Docstring', 'YARD', '']) expect(return_type).to be_defined expect(parser_method_pin.typify(api_map).rooted_tags).to eq('::YARD::DocstringParser') end diff --git a/spec/pin/base_variable_spec.rb b/spec/pin/base_variable_spec.rb index 03e6b1a11..9ca63f3dd 100644 --- a/spec/pin/base_variable_spec.rb +++ b/spec/pin/base_variable_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::Pin::BaseVariable do - it "checks assignments for equality" do + it 'checks assignments for equality' do smap = Solargraph::SourceMap.load_string('foo = "foo"') pin1 = smap.locals.first smap = Solargraph::SourceMap.load_string('foo = "foo"') @@ -45,9 +47,7 @@ def bar expect(type.simplify_literals.to_rbs).to eq('(::Integer | ::NilClass)') end - xit "understands proc kwarg parameters aren't affected by @type" do - pending "understanding restarg in block param in Block#typify_parameters" - + it "understands proc kwarg parameters aren't affected by @type" do code = %( # @return [Proc] def foo diff --git a/spec/pin/block_spec.rb b/spec/pin/block_spec.rb index 0ca36e950..7d7d1b028 100644 --- a/spec/pin/block_spec.rb +++ b/spec/pin/block_spec.rb @@ -1,7 +1,12 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Block do + let(:foo) { instance_double(Solargraph::Pin::Parameter, name: 'foo') } + let(:bar) { instance_double(Solargraph::Pin::Parameter, name: 'bar') } + let(:block) { instance_double(Solargraph::Pin::Parameter, name: 'block') } + it 'strips prefixes from parameter names' do - # @todo Method parameters are pins now - # pin = Solargraph::Pin::Block.new(args: ['foo', '*bar', '&block']) - # expect(pin.parameter_names).to eq(['foo', 'bar', 'block']) + pin = described_class.new(args: [foo, bar, block]) + expect(pin.parameter_names).to eq(%w[foo bar block]) end end diff --git a/spec/pin/class_variable_spec.rb b/spec/pin/class_variable_spec.rb index c327f01a1..686b13177 100644 --- a/spec/pin/class_variable_spec.rb +++ b/spec/pin/class_variable_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + # describe Solargraph::Pin::ClassVariable do # it "always has class scope" do # source = Solargraph::Source.load_string(%( diff --git a/spec/pin/constant_spec.rb b/spec/pin/constant_spec.rb index 7a7fdb0c9..116c72b2a 100644 --- a/spec/pin/constant_spec.rb +++ b/spec/pin/constant_spec.rb @@ -1,23 +1,25 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Constant do - it "resolves constant paths" do + it 'resolves constant paths' do source = Solargraph::Source.new(%( class Foo BAR = 'bar' end )) map = Solargraph::SourceMap.map(source) - pin = map.pins.select{|pin| pin.name == 'BAR'}.first + pin = map.pins.select { |pin| pin.name == 'BAR' }.first expect(pin.path).to eq('Foo::BAR') end - it "is a constant kind" do + it 'is a constant kind' do source = Solargraph::Source.new(%( class Foo BAR = 'bar' end )) map = Solargraph::SourceMap.map(source) - pin = map.pins.select{|pin| pin.name == 'BAR'}.first + pin = map.pins.select { |pin| pin.name == 'BAR' }.first expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::CONSTANT) expect(pin.symbol_kind).to eq(Solargraph::LanguageServer::SymbolKinds::CONSTANT) end diff --git a/spec/pin/delegated_method_spec.rb b/spec/pin/delegated_method_spec.rb index a20fc26d4..7063d42c0 100644 --- a/spec/pin/delegated_method_spec.rb +++ b/spec/pin/delegated_method_spec.rb @@ -1,10 +1,12 @@ +# frozen_string_literal: true + require 'pry' describe Solargraph::Pin::DelegatedMethod do it 'can be constructed from a Method pin' do method_pin = Solargraph::Pin::Method.new(comments: '@return [Hash{String => String}]') - delegation_pin = Solargraph::Pin::DelegatedMethod.new(method: method_pin, scope: :instance) + delegation_pin = described_class.new(method: method_pin, scope: :instance) expect(delegation_pin.return_type.to_s).to eq('Hash{String => String}') end @@ -26,7 +28,7 @@ def collaborator; end class2 = api_map.get_path_pins('Class2').first chain = Solargraph::Source::Chain.new([Solargraph::Source::Chain::Call.new('collaborator', nil)]) - pin = Solargraph::Pin::DelegatedMethod.new( + pin = described_class.new( closure: class2, scope: :instance, name: 'name', diff --git a/spec/pin/documenting_spec.rb b/spec/pin/documenting_spec.rb index 1fdf9b8de..cfecb0468 100644 --- a/spec/pin/documenting_spec.rb +++ b/spec/pin/documenting_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Documenting do - let(:object) { + let(:object) do Class.new do include Solargraph::Pin::Documenting attr_accessor :docstring end.new - } + end it 'parses indented code blocks' do object.docstring = YARD::Docstring.new(%(Method overview diff --git a/spec/pin/instance_variable_spec.rb b/spec/pin/instance_variable_spec.rb index fd6203398..e1b08e028 100644 --- a/spec/pin/instance_variable_spec.rb +++ b/spec/pin/instance_variable_spec.rb @@ -1,14 +1,16 @@ +# frozen_string_literal: true + describe Solargraph::Pin::InstanceVariable do - it "is a kind of variable" do + it 'is a kind of variable' do source = Solargraph::Source.load_string("@foo = 'foo'", 'file.rb') map = Solargraph::SourceMap.map(source) - pin = map.pins.select{ |p| p.is_a?(Solargraph::Pin::InstanceVariable) }.first + pin = map.pins.select { |p| p.is_a?(described_class) }.first expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::VARIABLE) expect(pin.symbol_kind).to eq(Solargraph::LanguageServer::SymbolKinds::VARIABLE) end - it "does not link documentation for undefined return types" do - pin = Solargraph::Pin::InstanceVariable.new(name: '@bar') + it 'does not link documentation for undefined return types' do + pin = described_class.new(name: '@bar') expect(pin.link_documentation).to eq('@bar') end end diff --git a/spec/pin/keyword_spec.rb b/spec/pin/keyword_spec.rb index 82bb373ef..4ffb9d34c 100644 --- a/spec/pin/keyword_spec.rb +++ b/spec/pin/keyword_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Keyword do - it "is a kind of keyword" do - pin = Solargraph::Pin::Keyword.new('foo') + it 'is a kind of keyword' do + pin = described_class.new('foo') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end end diff --git a/spec/pin/local_variable_spec.rb b/spec/pin/local_variable_spec.rb index 39fd22c28..d9e8590f9 100644 --- a/spec/pin/local_variable_spec.rb +++ b/spec/pin/local_variable_spec.rb @@ -1,5 +1,9 @@ +# frozen_string_literal: true + describe Solargraph::Pin::LocalVariable do - xit "merges presence changes so that [not currently used]" do + it 'merges presence changes so that [not currently used]' do + pending 'but not sure why' + map1 = Solargraph::SourceMap.load_string(%( class Foo foo = 'foo' @@ -26,7 +30,6 @@ class Foo expect { pin1.combine_with(pin2) }.to raise_error(RuntimeError, /Inconsistent :closure name/) end - expect(combined.source).to eq(:combined) # no choice behavior defined yet - if/when this is to be used, we # should indicate which one should override in the range situation @@ -40,7 +43,7 @@ class Foo ), 'test.rb') api_map = Solargraph::ApiMap.new api_map.map source - clip = api_map.clip_at('test.rb', [2, 0]) + api_map.clip_at('test.rb', [2, 0]) object_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'object' } expect(object_pin).not_to be_nil location = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 0, 2, 0)) @@ -85,14 +88,14 @@ class Foo; end expect(x_pin.visible_at?(block_pin, location)).to be true end - it "understands local lookup in root scope" do + it 'understands local lookup in root scope' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( # @type [Array] arr = [] - ), "test.rb") + ), 'test.rb') api_map.map source arr_pin = api_map.source_map('test.rb').locals.find { |p| p.name == 'arr' } expect(arr_pin).not_to be_nil @@ -142,7 +145,7 @@ def bar end it 'is visible within each block scope inside function' do - source = Solargraph::Source.load_string(%( + source = Solargraph::Source.load_string(%( class Foo def bar x = 1 @@ -152,17 +155,17 @@ def bar end end ), 'test.rb') - api_map = Solargraph::ApiMap.new - api_map.map source - x = api_map.source_map('test.rb').locals.find { |p| p.name == 'x' } - bar_method = api_map.get_path_pins('Foo#bar').first - each_block_pin = api_map.get_block_pins.find do |b| - b.location.range.start.line == 4 - end - expect(each_block_pin).not_to be_nil - range = Solargraph::Range.from_to(5, 24, 5, 25) - location = Solargraph::Location.new('test.rb', range) - expect(x.visible_at?(each_block_pin, location)).to be true + api_map = Solargraph::ApiMap.new + api_map.map source + x = api_map.source_map('test.rb').locals.find { |p| p.name == 'x' } + api_map.get_path_pins('Foo#bar').first + each_block_pin = api_map.get_block_pins.find do |b| + b.location.range.start.line == 4 + end + expect(each_block_pin).not_to be_nil + range = Solargraph::Range.from_to(5, 24, 5, 25) + location = Solargraph::Location.new('test.rb', range) + expect(x.visible_at?(each_block_pin, location)).to be true end it 'sees block parameter inside block' do diff --git a/spec/pin/method_spec.rb b/spec/pin/method_spec.rb index c2ef40448..c109746af 100644 --- a/spec/pin/method_spec.rb +++ b/spec/pin/method_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Method do it 'tracks code parameters' do source = Solargraph::Source.new(%( @@ -33,7 +35,7 @@ def foo bar:, baz: MyClass.new )) map = Solargraph::SourceMap.map(source) pin = map.pins.select { |pin| pin.path == '#foo' }.first - expect(pin.class).to eq(Solargraph::Pin::Method) + expect(pin.class).to eq(described_class) method_pin = pin expect(method_pin.signatures.length).to eq(1) method_signature = method_pin.signatures.first @@ -53,7 +55,7 @@ def foo bar:, baz: MyClass.new @param two [Second] description2 COMMENTS # pin = source.pins.select{|pin| pin.path == 'Foo#bar'}.first - pin = Solargraph::Pin::Method.new(comments: comments) + pin = described_class.new(comments: comments) expect(pin.documentation).to include('one') expect(pin.documentation).to include('[First]') expect(pin.documentation).to include('description1') @@ -77,9 +79,9 @@ def bazzle; end COMMENTS map = Solargraph::SourceMap.map(source) bazzle = map.pins.select { |pin| pin.path == 'Bar::Baz#bazzle' }.first - expect(bazzle.return_type.rooted?).to eq(false) + expect(bazzle.return_type.rooted?).to be(false) bing = map.pins.select { |pin| pin.path == 'Bar::Baz#bing' }.first - expect(bing.return_type.rooted?).to eq(true) + expect(bing.return_type.rooted?).to be(true) end it 'includes yieldparam tags in documentation' do @@ -87,7 +89,7 @@ def bazzle; end @yieldparam one [First] description1 @yieldparam two [Second] description2 COMMENTS - pin = Solargraph::Pin::Method.new(comments: comments) + pin = described_class.new(comments: comments) expect(pin.documentation).to include('one') expect(pin.documentation).to include('[First]') expect(pin.documentation).to include('description1') @@ -101,37 +103,37 @@ def bazzle; end @yieldreturn [YRet] yretdescription @return [String] COMMENTS - pin = Solargraph::Pin::Method.new(comments: comments) + pin = described_class.new(comments: comments) expect(pin.documentation).to include('YRet') expect(pin.documentation).to include('yretdescription') end it 'detects return types from tags' do - pin = Solargraph::Pin::Method.new(comments: '@return [Hash]') + pin = described_class.new(comments: '@return [Hash]') expect(pin.return_type.tag).to eq('Hash') end it 'ignores malformed return tags' do - pin = Solargraph::Pin::Method.new(name: 'bar', comments: '@return [Array) @@ -425,7 +421,7 @@ def bar?; end end it 'supports multiple return tags' do - pin = Solargraph::Pin::Method.new( + pin = described_class.new( name: 'foo', comments: %( @return [String] @@ -436,7 +432,7 @@ def bar?; end end it 'includes @return text in documentation' do - pin = Solargraph::Pin::Method.new( + pin = described_class.new( name: 'foo', comments: %( @return [String] the foo text string @@ -446,7 +442,7 @@ def bar?; end end it 'includes @example text in documentation' do - pin = Solargraph::Pin::Method.new( + pin = described_class.new( name: 'foo', comments: %( @example @@ -458,7 +454,7 @@ def bar?; end end it 'includes @example names' do - pin = Solargraph::Pin::Method.new( + pin = described_class.new( name: 'foo', comments: %( @example Call foo @@ -485,8 +481,8 @@ def bar param1, param2 api_map.map source pin = api_map.get_path_pins('Example#bar').first pin.resolve_ref_tag(api_map) - expect(pin.docstring.tags(:param).map(&:name)).to eq(['param1', 'param2']) - expect(pin.docstring.tags(:param).map(&:type)).to eq(['String', 'Integer']) + expect(pin.docstring.tags(:param).map(&:name)).to eq(%w[param1 param2]) + expect(pin.docstring.tags(:param).map(&:type)).to eq(%w[String Integer]) end it 'resolves ref tags with namespaces' do @@ -508,11 +504,11 @@ def bar param1, param2 api_map.map source pin = api_map.get_path_pins('Example2#bar').first pin.resolve_ref_tag(api_map) - expect(pin.docstring.tags(:param).map(&:name)).to eq(['param1', 'param2']) - expect(pin.docstring.tags(:param).map(&:type)).to eq(['String', 'Integer']) + expect(pin.docstring.tags(:param).map(&:name)).to eq(%w[param1 param2]) + expect(pin.docstring.tags(:param).map(&:type)).to eq(%w[String Integer]) end - context 'as attribute' do + context 'when attr_reader is used' do it 'is a kind of attribute/property' do source = Solargraph::Source.load_string(%( class Foo @@ -520,32 +516,32 @@ class Foo end )) map = Solargraph::SourceMap.map(source) - pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::Method) }.first + pin = map.pins.select { |p| p.is_a?(described_class) }.first expect(pin).to be_attribute expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::PROPERTY) expect(pin.symbol_kind).to eq(Solargraph::LanguageServer::SymbolKinds::PROPERTY) end it 'uses return type tags' do - pin = Solargraph::Pin::Method.new(name: 'bar', comments: '@return [File]', attribute: true) + pin = described_class.new(name: 'bar', comments: '@return [File]', attribute: true) expect(pin.return_type.tag).to eq('File') end it 'detects undefined types' do - pin = Solargraph::Pin::Method.new(name: 'bar', attribute: true) + pin = described_class.new(name: 'bar', attribute: true) expect(pin.return_type).to be_undefined end it 'generates paths' do npin = Solargraph::Pin::Namespace.new(name: 'Foo', type: :class) - ipin = Solargraph::Pin::Method.new(closure: npin, name: 'bar', attribute: true, scope: :instance) + ipin = described_class.new(closure: npin, name: 'bar', attribute: true, scope: :instance) expect(ipin.path).to eq('Foo#bar') - cpin = Solargraph::Pin::Method.new(closure: npin, name: 'bar', attribute: true, scope: :class) + cpin = described_class.new(closure: npin, name: 'bar', attribute: true, scope: :class) expect(cpin.path).to eq('Foo.bar') end it 'handles invalid return type tags' do - pin = Solargraph::Pin::Method.new(name: 'bar', comments: '@return [Array<]', attribute: true) + pin = described_class.new(name: 'bar', comments: '@return [Array<]', attribute: true) expect(pin.return_type).to be_undefined end @@ -632,7 +628,7 @@ def bar end it 'ignores malformed overload tags' do - pin = Solargraph::Pin::Method.new(name: 'example', comments: "@overload\n @param") + pin = described_class.new(name: 'example', comments: "@overload\n @param") expect(pin.overloads).to be_empty end end diff --git a/spec/pin/namespace_spec.rb b/spec/pin/namespace_spec.rb index 3c94a526e..cafadcc58 100644 --- a/spec/pin/namespace_spec.rb +++ b/spec/pin/namespace_spec.rb @@ -1,34 +1,36 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Namespace do - it "handles long namespaces" do - pin = Solargraph::Pin::Namespace.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), name: 'Bar') + it 'handles long namespaces' do + pin = described_class.new(closure: described_class.new(name: 'Foo'), name: 'Bar') expect(pin.path).to eq('Foo::Bar') end - it "has class scope" do - source = Solargraph::Source.load_string(%( + it 'has class scope' do + Solargraph::Source.load_string(%( class Foo end )) - pin = Solargraph::Pin::Namespace.new(name: 'Foo') + pin = described_class.new(name: 'Foo') expect(pin.context.scope).to eq(:class) end - it "is a kind of namespace/class/module" do - pin1 = Solargraph::Pin::Namespace.new(name: 'Foo') + it 'is a kind of namespace/class/module' do + pin1 = described_class.new(name: 'Foo') expect(pin1.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::CLASS) - pin2 = Solargraph::Pin::Namespace.new(name: 'Foo', type: :module) + pin2 = described_class.new(name: 'Foo', type: :module) expect(pin2.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::MODULE) end it 'handles nested namespaces inside closures' do - pin = Solargraph::Pin::Namespace.new(closure: Solargraph::Pin::Namespace.new(name: 'Foo'), name: 'Bar::Baz') + pin = described_class.new(closure: described_class.new(name: 'Foo'), name: 'Bar::Baz') expect(pin.namespace).to eq('Foo::Bar') expect(pin.name).to eq('Baz') expect(pin.path).to eq('Foo::Bar::Baz') end it 'uses @param tags as generic type parameters' do - pin = Solargraph::Pin::Namespace.new(name: 'Foo', comments: '@generic GenericType') + pin = described_class.new(name: 'Foo', comments: '@generic GenericType') expect(pin.generics).to eq(['GenericType']) expect(pin.to_rbs).to eq('class ::Foo[GenericType]') end diff --git a/spec/pin/parameter_spec.rb b/spec/pin/parameter_spec.rb index 14c39f3fe..971acc10e 100644 --- a/spec/pin/parameter_spec.rb +++ b/spec/pin/parameter_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Parameter do it 'detects block parameter return types from @yieldparam tags' do api_map = Solargraph::ApiMap.new @@ -195,8 +197,8 @@ def baz; end it 'uses longer comment while combining compatible parameters' do loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(0, 0, 0, 0)) block = Solargraph::Pin::Block.new(location: loc, name: 'Foo') - pin1 = Solargraph::Pin::Parameter.new(closure: block, name: 'bar') - pin2 = Solargraph::Pin::Parameter.new(closure: block, name: 'bar', comments: 'a comment') + pin1 = described_class.new(closure: block, name: 'bar') + pin2 = described_class.new(closure: block, name: 'bar', comments: 'a comment') expect(pin1.combine_with(pin2).comments).to eq('a comment') end @@ -207,7 +209,7 @@ def baz; end ), 'test.rb') api_map = Solargraph::ApiMap.new api_map.map source - pin = api_map.source_map('test.rb').locals.select { |p| p.is_a?(Solargraph::Pin::Parameter) }.first + pin = api_map.source_map('test.rb').locals.select { |p| p.is_a?(described_class) }.first # expect(pin.infer(api_map)).to be_undefined expect(pin.typify(api_map)).to be_undefined expect(pin.probe(api_map)).to be_undefined @@ -367,7 +369,7 @@ def use_string str expect(type.tag).to eq('String') end - context 'for instance methods' do + context 'when used in an instance methods' do it 'infers types from optarg values' do source = Solargraph::Source.load_string(%( class Example @@ -397,7 +399,7 @@ def foo bar: 'bar' end end - context 'for class methods' do + context 'when used in an class method' do it 'infers types from optarg values' do source = Solargraph::Source.load_string(%( class Example @@ -441,7 +443,7 @@ def self.foo bar: Hash.new end end - context 'for singleton methods' do + context 'when used in a singleton method' do it 'infers types from optarg values' do source = Solargraph::Source.load_string(%( class Example diff --git a/spec/pin/search_spec.rb b/spec/pin/search_spec.rb index 28c302839..f505f45e9 100644 --- a/spec/pin/search_spec.rb +++ b/spec/pin/search_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Search do it 'returns ordered matches on paths' do example_class = Solargraph::Pin::Namespace.new(name: 'Example') @@ -6,7 +8,7 @@ Solargraph::Pin::Method.new(name: 'foobar', closure: example_class), Solargraph::Pin::Method.new(name: 'foo_bar', closure: example_class) ] - search = Solargraph::Pin::Search.new(pins, 'example') + search = described_class.new(pins, 'example') expect(search.results).to eq(pins) end @@ -17,7 +19,7 @@ Solargraph::Pin::Method.new(name: 'foobar', closure: example_class), Solargraph::Pin::Method.new(name: 'foo_bar', closure: example_class) ] - search = Solargraph::Pin::Search.new(pins, 'foobar') + search = described_class.new(pins, 'foobar') expect(search.results.map(&:path)).to eq(['Example.foobar', 'Example.foo_bar']) end @@ -28,7 +30,7 @@ Solargraph::Pin::Method.new(name: 'foobar', closure: example_class), Solargraph::Pin::Method.new(name: 'bazquz', closure: example_class) ] - search = Solargraph::Pin::Search.new(pins, 'foobar') + search = described_class.new(pins, 'foobar') expect(search.results.map(&:path)).to eq(['Example.foobar']) end end diff --git a/spec/pin/symbol_spec.rb b/spec/pin/symbol_spec.rb index 16961cadc..585c8b5b1 100644 --- a/spec/pin/symbol_spec.rb +++ b/spec/pin/symbol_spec.rb @@ -1,54 +1,55 @@ +# frozen_string_literal: true + describe Solargraph::Pin::Symbol do - context "as an unquoted literal" do - it "is a kind of keyword to the LSP" do - pin = Solargraph::Pin::Symbol.new(nil, ':symbol') + context 'when an unquoted literal' do + it 'is a kind of keyword to the LSP' do + pin = described_class.new(nil, ':symbol') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end - it "has global closure" do - pin = Solargraph::Pin::Symbol.new(nil, ':symbol') + it 'has global closure' do + pin = described_class.new(nil, ':symbol') expect(pin.closure).to eq(Solargraph::Pin::ROOT_PIN) end - it "has a Symbol return type" do - pin = Solargraph::Pin::Symbol.new(nil, ':symbol') + it 'has a Symbol return type' do + pin = described_class.new(nil, ':symbol') expect(pin.return_type.tag).to eq('Symbol') end end - context "as a double quoted literal" do - it "is a kind of keyword" do - pin = Solargraph::Pin::Symbol.new(nil, ':"symbol"') + context 'when a double quoted literal' do + it 'is a kind of keyword' do + pin = described_class.new(nil, ':"symbol"') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end - it "has a Symbol return type" do - pin = Solargraph::Pin::Symbol.new(nil, ':"symbol"') + it 'has a Symbol return type' do + pin = described_class.new(nil, ':"symbol"') expect(pin.return_type.tag).to eq('Symbol') end end - context "as a double quoted interpolatd literal" do - it "is a kind of keyword" do - pin = Solargraph::Pin::Symbol.new(nil, ':"symbol #{variable}"') + context 'when a double quoted interpolated literal' do + it 'is a kind of keyword' do + pin = described_class.new(nil, ':"symbol #{variable}"') expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end - it "has a Symbol return type" do - pin = Solargraph::Pin::Symbol.new(nil, ':"symbol #{variable}"') + it 'has a Symbol return type' do + pin = described_class.new(nil, ':"symbol #{variable}"') expect(pin.return_type.tag).to eq('Symbol') end end - - context "as a single quoted literal" do - it "is a kind of keyword" do - pin = Solargraph::Pin::Symbol.new(nil, ":'symbol'") + context 'when a single quoted literal' do + it 'is a kind of keyword' do + pin = described_class.new(nil, ":'symbol'") expect(pin.completion_item_kind).to eq(Solargraph::LanguageServer::CompletionItemKinds::KEYWORD) end - it "has a Symbol return type" do - pin = Solargraph::Pin::Symbol.new(nil, ":'symbol'") + it 'has a Symbol return type' do + pin = described_class.new(nil, ":'symbol'") expect(pin.return_type.tag).to eq('Symbol') end end diff --git a/spec/pin_cache_spec.rb b/spec/pin_cache_spec.rb index 0f766117a..83cf7a3b7 100644 --- a/spec/pin_cache_spec.rb +++ b/spec/pin_cache_spec.rb @@ -150,7 +150,8 @@ pin_cache.cache_gem(gemspec: yaml_gemspec, out: nil) # match arguments with regexp using rspec-matchers syntax - expect(File).to have_received(:write).with(%r{combined/.*/rubocop-yard-.*-export.ser$}, any_args, mode: 'wb').once + expect(File).to have_received(:write).with(%r{combined/.*/rubocop-yard-.*-export.ser$}, any_args, + mode: 'wb').once end end end diff --git a/spec/position_spec.rb b/spec/position_spec.rb index fa30cf7d9..d61b05ce5 100644 --- a/spec/position_spec.rb +++ b/spec/position_spec.rb @@ -1,20 +1,51 @@ +# frozen_string_literal: true + describe Solargraph::Position do - it "normalizes arrays into positions" do - pos = Solargraph::Position.normalize([0, 1]) - expect(pos).to be_a(Solargraph::Position) + it 'normalizes arrays into positions' do + pos = described_class.normalize([0, 1]) + expect(pos).to be_a(described_class) expect(pos.line).to eq(0) expect(pos.column).to eq(1) end - it "returns original positions when normalizing" do - orig = Solargraph::Position.new(0, 1) - norm = Solargraph::Position.normalize(orig) + it 'returns original positions when normalizing' do + orig = described_class.new(0, 1) + norm = described_class.normalize(orig) expect(orig).to be(norm) end - it "raises an error for objects that cannot be normalized" do - expect { - Solargraph::Position.normalize('0, 1') - }.to raise_error(ArgumentError) + it 'finds offset from position' do + text = "\n class Foo\n def bar baz, boo = 'boo'\n end\n end\n " + expect(described_class.to_offset(text, described_class.new(0, 0))).to eq(0) + expect(described_class.to_offset(text, described_class.new(0, 4))).to eq(4) + expect(described_class.to_offset(text, described_class.new(2, 12))).to eq(29) + expect(described_class.to_offset(text, described_class.new(2, 27))).to eq(44) + expect(described_class.to_offset(text, described_class.new(3, 8))).to eq(58) + end + + it 'constructs position from offset' do + text = "\n class Foo\n def bar baz, boo = 'boo'\n end\n end\n " + expect(described_class.from_offset(text, 0)).to eq(described_class.new(0, 0)) + expect(described_class.from_offset(text, 4)).to eq(described_class.new(1, 3)) + expect(described_class.from_offset(text, 29)).to eq(described_class.new(2, 12)) + expect(described_class.from_offset(text, 44)).to eq(described_class.new(2, 27)) + end + + it 'raises an error for objects that cannot be normalized' do + expect do + described_class.normalize('0, 1') + end.to raise_error(ArgumentError) + end + + it 'avoids fencepost errors' do + text = " class Foo\n def bar baz, boo = 'boo'\n end\n end\n " + offset = described_class.to_offset(text, described_class.new(3, 6)) + expect(offset).to eq(67) + end + + it 'avoids fencepost errors with multiple blank lines' do + text = " class Foo\n def bar baz, boo = 'boo'\n\n end\n end\n " + offset = described_class.to_offset(text, described_class.new(4, 6)) + expect(offset).to eq(68) end end diff --git a/spec/rbs_map/conversions_spec.rb b/spec/rbs_map/conversions_spec.rb index 61771ac10..e9dc1ccf8 100644 --- a/spec/rbs_map/conversions_spec.rb +++ b/spec/rbs_map/conversions_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + describe Solargraph::RbsMap::Conversions do context 'with RBS to digest' do # create a temporary directory with the scope of the spec around do |example| require 'tmpdir' - Dir.mktmpdir("rspec-solargraph-") do |dir| + Dir.mktmpdir('rspec-solargraph-') do |dir| @temp_dir = dir example.run end @@ -12,7 +14,7 @@ let(:conversions) do loader = RBS::EnvironmentLoader.new(core_root: nil, repository: RBS::Repository.new(no_stdlib: false)) loader.add(path: Pathname(temp_dir)) - Solargraph::RbsMap::Conversions.new(loader: loader) + described_class.new(loader: loader) end let(:api_map) { Solargraph::ApiMap.new } diff --git a/spec/rbs_map/core_map_spec.rb b/spec/rbs_map/core_map_spec.rb index cada2754c..79878c572 100644 --- a/spec/rbs_map/core_map_spec.rb +++ b/spec/rbs_map/core_map_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::RbsMap::CoreMap do it 'maps core Errno classes' do - map = Solargraph::RbsMap::CoreMap.new + map = described_class.new store = Solargraph::ApiMap::Store.new(map.pins) Errno.constants.each do |const| pin = store.get_path_pins("Errno::#{const}").first @@ -12,20 +14,20 @@ end it 'understands RBS class aliases' do - map = Solargraph::RbsMap::CoreMap.new + map = described_class.new store = Solargraph::ApiMap::Store.new(map.pins) # The core RBS contains: # class Mutex = Thread::Mutex - thread_mutex_pin = store.get_path_pins("Thread::Mutex").first + thread_mutex_pin = store.get_path_pins('Thread::Mutex').first expect(thread_mutex_pin).to be_a(Solargraph::Pin::Namespace) - mutex_pin = store.get_path_pins("Mutex").first + mutex_pin = store.get_path_pins('Mutex').first expect(mutex_pin).to be_a(Solargraph::Pin::Constant) - expect(mutex_pin.return_type.to_s).to eq("Class") + expect(mutex_pin.return_type.to_s).to eq('Class') end it 'understands RBS global variables' do - map = Solargraph::RbsMap::CoreMap.new + map = described_class.new store = Solargraph::ApiMap::Store.new(map.pins) global_variable_pins = store.pins_by_class(Solargraph::Pin::GlobalVariable) stderr_pins = global_variable_pins.select do |pin| @@ -39,10 +41,10 @@ it 'understands implied Enumerator#each method' do api_map = Solargraph::ApiMap.new methods = api_map.get_methods('Enumerable') - each_pins = methods.select{|pin| pin.path.end_with?('#each')} + each_pins = methods.select { |pin| pin.path.end_with?('#each') } # expect this to come from the _Each implied interface ("self # type") defined in the RBS - expect(each_pins.map(&:path)).to eq(["_Each#each"]) + expect(each_pins.map(&:path)).to eq(['_Each#each']) expect(each_pins.map(&:class)).to eq([Solargraph::Pin::Method]) each_pin = each_pins.first expect(each_pin.signatures.length).to eq(1) @@ -53,7 +55,7 @@ it 'populates types in block parameters from generics' do api_map = Solargraph::ApiMap.new methods = api_map.get_methods('Enumerable') - each_pins = methods.select{|pin| pin.path.end_with?('#each')} + each_pins = methods.select { |pin| pin.path.end_with?('#each') } each_pin = each_pins.first signature = each_pin.signatures.first expect(signature.block.parameters.map(&:return_type).map(&:to_s)).to eq(['String']) @@ -69,7 +71,7 @@ # api_map = Solargraph::ApiMap.new methods = api_map.get_methods('Enumerable') - each_pins = methods.select{|pin| pin.path.end_with?('#each')} + each_pins = methods.select { |pin| pin.path.end_with?('#each') } each_pin = each_pins.first signature = each_pin.signatures.first expect(signature.return_type.to_s).to eq('Enumerable') @@ -79,8 +81,8 @@ # @todo This is a simple smoke test to ensure that mixins are applied # correctly. It would be better to test RbsMap or RbsMap::Conversions # with an RBS fixture. - core_map = Solargraph::RbsMap::CoreMap.new - pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == 'Enumerable' } + core_map = described_class.new + pins = core_map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.name == '::Enumerable' } expect(pins.map(&:closure).map(&:namespace)).to include('Enumerator') end @@ -97,16 +99,6 @@ class Foo expect(clip.infer.to_s).to eq('Foo') end - it "generates rooted pins from RBS for core" do - map = Solargraph::RbsMap::CoreMap.new - map.pins.each do |pin| - expect(pin).to be_all_rooted - unless pin.is_a?(Solargraph::Pin::Keyword) - expect(pin.closure).to_not be_nil, ->(){ "Pin #{pin.inspect} (#{pin.path}) has no closure" } - end - end - end - it 'renders string literals from RBS in a useful way' do source = Solargraph::Source.load_string(%( foo = nil diff --git a/spec/rbs_map/stdlib_map_spec.rb b/spec/rbs_map/stdlib_map_spec.rb index c9db9bb48..00afc6542 100644 --- a/spec/rbs_map/stdlib_map_spec.rb +++ b/spec/rbs_map/stdlib_map_spec.rb @@ -1,18 +1,20 @@ +# frozen_string_literal: true + describe Solargraph::RbsMap::StdlibMap do - it "finds stdlib require paths" do - rbs_map = Solargraph::RbsMap::StdlibMap.load('fileutils') + it 'finds stdlib require paths' do + rbs_map = described_class.load('fileutils') pin = rbs_map.path_pin('FileUtils#chdir') - expect(pin).to be + expect(pin).not_to be_nil end it 'maps YAML' do - rbs_map = Solargraph::RbsMap::StdlibMap.load('yaml') + rbs_map = described_class.load('yaml') pin = rbs_map.path_pin('YAML') expect(pin).to be_a(Solargraph::Pin::Base) end it 'processes RBS module aliases' do - map = Solargraph::RbsMap::StdlibMap.load('yaml') + map = described_class.load('yaml') store = Solargraph::ApiMap::Store.new(map.pins) constant_pins = store.get_constants('') yaml_pins = constant_pins.select do |pin| @@ -25,7 +27,7 @@ end it 'pins are marked as coming from RBS parsing' do - map = Solargraph::RbsMap::StdlibMap.load('yaml') + map = described_class.load('yaml') store = Solargraph::ApiMap::Store.new(map.pins) constant_pins = store.get_constants('') pin = constant_pins.first diff --git a/spec/rbs_map_spec.rb b/spec/rbs_map_spec.rb index 4631c9ca5..09e7a1a80 100644 --- a/spec/rbs_map_spec.rb +++ b/spec/rbs_map_spec.rb @@ -1,25 +1,27 @@ +# frozen_string_literal: true + describe Solargraph::RbsMap do it 'loads from a gemspec' do spec = Gem::Specification.find_by_name('rbs') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) pin = rbs_map.path_pin('RBS::EnvironmentLoader#add_collection') expect(pin).not_to be_nil end it 'fails if it does not find data from gemspec' do spec = Gem::Specification.find_by_name('backport') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) expect(rbs_map).not_to be_resolved end it 'fails if it does not find data from name' do - rbs_map = Solargraph::RbsMap.new('lskdflaksdfjl') + rbs_map = described_class.new('lskdflaksdfjl') expect(rbs_map.pins).to be_empty end it 'converts constants and aliases to correct types' do spec = Gem::Specification.find_by_name('rbs') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) pin = rbs_map.path_pin('RBS::EnvironmentLoader::DEFAULT_CORE_ROOT') expect(pin.return_type.tag).to eq('Pathname') pin = rbs_map.path_pin('RBS::EnvironmentWalker::InstanceNode') @@ -28,7 +30,7 @@ it 'processes RBS class variables' do spec = Gem::Specification.find_by_name('rbs') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) store = Solargraph::ApiMap::Store.new(rbs_map.pins) class_variable_pins = store.pins_by_class(Solargraph::Pin::ClassVariable) count_pins = class_variable_pins.select do |pin| @@ -41,7 +43,7 @@ it 'processes RBS class instance variables' do spec = Gem::Specification.find_by_name('rbs') - rbs_map = Solargraph::RbsMap.from_gemspec(spec, nil, nil) + rbs_map = described_class.from_gemspec(spec, nil, nil) store = Solargraph::ApiMap::Store.new(rbs_map.pins) instance_variable_pins = store.pins_by_class(Solargraph::Pin::InstanceVariable) root_pins = instance_variable_pins.select do |pin| diff --git a/spec/shell_spec.rb b/spec/shell_spec.rb index aca90eb7e..e37b3d9b6 100644 --- a/spec/shell_spec.rb +++ b/spec/shell_spec.rb @@ -10,10 +10,10 @@ before do File.open(File.join(temp_dir, 'Gemfile'), 'w') do |file| - file.puts "source 'https://rubygems.org'" - file.puts "gem 'solargraph', path: '#{File.expand_path('..', __dir__)}'" + file.puts "source 'https://rubygems.org'" + file.puts "gem 'solargraph', path: '#{File.expand_path('..', __dir__)}'" end - output, status = Open3.capture2e("bundle install", chdir: temp_dir) + output, status = Open3.capture2e('bundle install', chdir: temp_dir) raise "Failure installing bundle: #{output}" unless status.success? end diff --git a/spec/source/chain/array_spec.rb b/spec/source/chain/array_spec.rb index b8ef9db23..bb93820cd 100644 --- a/spec/source/chain/array_spec.rb +++ b/spec/source/chain/array_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Array do - it "resolves an instance of an array" do + it 'resolves an instance of an array' do literal = described_class.new([], nil) pin = literal.resolve(nil, nil, nil).first expect(pin.return_type.tag).to eq('Array') diff --git a/spec/source/chain/call_spec.rb b/spec/source/chain/call_spec.rb index eaedfa998..122cc2ed7 100644 --- a/spec/source/chain/call_spec.rb +++ b/spec/source/chain/call_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Call do - it "recognizes core methods that return subtypes" do + it 'recognizes core methods that return subtypes' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( # @type [Array] @@ -12,7 +14,7 @@ expect(type.tag).to eq('String') end - it "recognizes core methods that return self" do + it 'recognizes core methods that return self' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( arr = [] @@ -24,7 +26,7 @@ expect(type.tag).to eq('Array') end - it "handles super calls to same method" do + it 'handles super calls to same method' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo @@ -44,7 +46,7 @@ def my_method expect(type.tag).to eq('Integer') end - it "infers return types based on yield call and @yieldreturn" do + it 'infers return types based on yield call and @yieldreturn' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo @@ -61,7 +63,7 @@ def my_method(&block) expect(type.tag).to eq('Integer') end - it "infers return types based only on yield call and @yieldreturn" do + it 'infers return types based only on yield call and @yieldreturn' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo @@ -78,10 +80,11 @@ def my_method(&block) expect(type.tag).to eq('Integer') end - it "adds virtual constructors for .new calls with conflicting return types" do + it 'adds virtual constructors for .new calls with conflicting return types' do api_map = Solargraph::ApiMap.new source = Solargraph::Source.load_string(%( class Foo + # @return [String] def self.new; end end Foo.new @@ -89,12 +92,10 @@ def self.new; end api_map.map source chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(4, 11)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map(nil).locals) - # @todo This test looks invalid now. If `Foo.new` is an empty method, - # shouldn't it return `nil` or `undefined`? - # expect(type.tag).to eq('Foo') + expect(type.tag).to eq('String') end - it "infers types from macros" do + it 'infers types from macros' do source = Solargraph::Source.load_string(%( class Foo # @!macro @@ -110,7 +111,7 @@ def self.bar; end expect(type.tag).to eq('String') end - it 'infers generic types' do + it 'infers generic types from Array#reverse' do source = Solargraph::Source.load_string(%( # @type [Array] list = array_of_strings @@ -292,7 +293,7 @@ def baz(&block) expect(type.simple_tags).to eq('Integer') end - it 'infers generic types' do + it 'infers generic types from @generic tag' do source = Solargraph::Source.load_string(%( # @generic GenericTypeParam class Foo @@ -576,7 +577,7 @@ def d expect(type.rooted_tags).not_to eq('::A::C') end - it 'qualifies types in a second Array#+ ' do + it 'qualifies types in a second Array#+' do source = Solargraph::Source.load_string(%( module A1 class B1 diff --git a/spec/source/chain/class_variable_spec.rb b/spec/source/chain/class_variable_spec.rb index 4121e9948..67a75e0c4 100644 --- a/spec/source/chain/class_variable_spec.rb +++ b/spec/source/chain/class_variable_spec.rb @@ -1,9 +1,11 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::ClassVariable do - it "resolves class variable pins" do + it 'resolves class variable pins' do foo_pin = Solargraph::Pin::ClassVariable.new(name: '@@foo') bar_pin = Solargraph::Pin::ClassVariable.new(name: '@@bar') - api_map = double(Solargraph::ApiMap, :get_class_variable_pins => [foo_pin, bar_pin]) - link = Solargraph::Source::Chain::ClassVariable.new('@@bar') + api_map = instance_double(Solargraph::ApiMap, get_class_variable_pins: [foo_pin, bar_pin]) + link = described_class.new('@@bar') pins = link.resolve(api_map, Solargraph::Pin::ROOT_PIN, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@@bar') diff --git a/spec/source/chain/constant_spec.rb b/spec/source/chain/constant_spec.rb index 4376650b3..6fbf54bff 100644 --- a/spec/source/chain/constant_spec.rb +++ b/spec/source/chain/constant_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Constant do - it "resolves constants in the current context" do + it 'resolves constants in the current context' do foo_pin = Solargraph::Pin::Constant.new(name: 'Foo', closure: Solargraph::Pin::ROOT_PIN) api_map = Solargraph::ApiMap.new api_map.index [foo_pin] diff --git a/spec/source/chain/global_variable_spec.rb b/spec/source/chain/global_variable_spec.rb index 4c5bd6bce..ec89fd599 100644 --- a/spec/source/chain/global_variable_spec.rb +++ b/spec/source/chain/global_variable_spec.rb @@ -1,11 +1,13 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::GlobalVariable do - it "resolves instance variable pins" do + it 'resolves instance variable pins' do closure = Solargraph::Pin::Namespace.new(name: 'Foo') foo_pin = Solargraph::Pin::GlobalVariable.new(closure: closure, name: '$foo') not_pin = Solargraph::Pin::InstanceVariable.new(closure: closure, name: '@bar') api_map = Solargraph::ApiMap.new api_map.index [foo_pin, not_pin] - link = Solargraph::Source::Chain::GlobalVariable.new('$foo') + link = described_class.new('$foo') pins = link.resolve(api_map, Solargraph::ComplexType.parse('Foo'), []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('$foo') diff --git a/spec/source/chain/head_spec.rb b/spec/source/chain/head_spec.rb index cc63a54a3..6526d4747 100644 --- a/spec/source/chain/head_spec.rb +++ b/spec/source/chain/head_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Head do - it "returns self pins" do - head = Solargraph::Source::Chain::Head.new('self') + it 'returns self pins' do + head = described_class.new('self') npin = Solargraph::Pin::ProxyType.anonymous(Solargraph::ComplexType.parse('Foo')) ipin = head.resolve(nil, npin, []).first expect(ipin.return_type.namespace).to eq('Foo') diff --git a/spec/source/chain/instance_variable_spec.rb b/spec/source/chain/instance_variable_spec.rb index ee4604f91..29ef714ee 100644 --- a/spec/source/chain/instance_variable_spec.rb +++ b/spec/source/chain/instance_variable_spec.rb @@ -1,7 +1,10 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::InstanceVariable do - it "resolves instance variable pins" do + it 'resolves instance variable pins' do closure = Solargraph::Pin::Namespace.new(name: 'Foo', - location: Solargraph::Location.new('test.rb', Solargraph::Range.from_to(1, 1, 9, 0)), + location: Solargraph::Location.new('test.rb', + Solargraph::Range.from_to(1, 1, 9, 0)), source: :closure) methpin = Solargraph::Pin::Method.new(closure: closure, name: 'imeth', scope: :instance, location: Solargraph::Location.new('test.rb', Solargraph::Range.from_to(1, 1, 3, 0)), @@ -17,7 +20,8 @@ api_map = Solargraph::ApiMap.new api_map.index [closure, methpin, foo_pin, bar_pin] - link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 2, 2, 3))) + link = described_class.new('@foo', nil, + Solargraph::Location.new('test.rb', Solargraph::Range.from_to(2, 2, 2, 3))) pins = link.resolve(api_map, methpin, []) expect(pins.length).to eq(1) @@ -27,7 +31,8 @@ name_pin = Solargraph::Pin::ProxyType.anonymous(closure.binder, # Closure is the class closure: closure) - link = Solargraph::Source::Chain::InstanceVariable.new('@foo', nil, Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 1, 5, 2))) + link = described_class.new('@foo', nil, + Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 1, 5, 2))) pins = link.resolve(api_map, name_pin, []) expect(pins.length).to eq(1) expect(pins.first.name).to eq('@foo') diff --git a/spec/source/chain/link_spec.rb b/spec/source/chain/link_spec.rb index 39143dbbd..b303ee5b8 100644 --- a/spec/source/chain/link_spec.rb +++ b/spec/source/chain/link_spec.rb @@ -1,26 +1,28 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Link do - it "is undefined by default" do + it 'is undefined by default' do link = described_class.new expect(link).to be_undefined end - it "is not a constant by default" do + it 'is not a constant by default' do link = described_class.new expect(link).not_to be_constant end - it "resolves empty arrays by default" do + it 'resolves empty arrays by default' do link = described_class.new expect(link.resolve(nil, nil, nil)).to be_empty end - it "recognizes equivalent links" do + it 'recognizes equivalent links' do l1 = described_class.new('foo') l2 = described_class.new('foo') expect(l1).to eq(l2) end - it "recognizes inequivalent links" do + it 'recognizes inequivalent links' do l1 = described_class.new('foo') l2 = described_class.new('bar') expect(l1).not_to eq(l2) diff --git a/spec/source/chain/literal_spec.rb b/spec/source/chain/literal_spec.rb index a1431ae07..ce75772c5 100644 --- a/spec/source/chain/literal_spec.rb +++ b/spec/source/chain/literal_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Literal do - it "resolves an instance of a literal" do + it 'resolves an instance of a literal' do literal = described_class.new('String', nil) api_map = Solargraph::ApiMap.new pin = literal.resolve(api_map, nil, nil).first diff --git a/spec/source/chain/or_spec.rb b/spec/source/chain/or_spec.rb index 084738fe3..4a36dfe7c 100644 --- a/spec/source/chain/or_spec.rb +++ b/spec/source/chain/or_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::Or do it 'handles simple nil-removal' do source = Solargraph::Source.load_string(%( diff --git a/spec/source/chain/q_call_spec.rb b/spec/source/chain/q_call_spec.rb index a63568358..21994fb2d 100644 --- a/spec/source/chain/q_call_spec.rb +++ b/spec/source/chain/q_call_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::QCall do it 'understands &. in chains' do source = Solargraph::Source.load_string(%( diff --git a/spec/source/chain/z_super_spec.rb b/spec/source/chain/z_super_spec.rb index aa412dda6..fc6f5a554 100644 --- a/spec/source/chain/z_super_spec.rb +++ b/spec/source/chain/z_super_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::Source::Chain::ZSuper do - it "resolves super" do - head = Solargraph::Source::Chain::ZSuper.new('super') + it 'resolves super' do + head = described_class.new('super') npin = Solargraph::Pin::Namespace.new(name: 'Substring') scpin = Solargraph::Pin::Reference::Superclass.new(closure: npin, name: 'String') mpin = Solargraph::Pin::Method.new(closure: npin, name: 'upcase', scope: :instance, visibility: :public) diff --git a/spec/source/chain_spec.rb b/spec/source/chain_spec.rb index 6db1686d4..4cccd285c 100644 --- a/spec/source/chain_spec.rb +++ b/spec/source/chain_spec.rb @@ -1,12 +1,12 @@ describe Solargraph::Source::Chain do it "gets empty definitions for undefined links" do chain = described_class.new([Solargraph::Source::Chain::Link.new]) - expect(chain.define(nil, nil, nil)).to be_empty + expect(chain.define(nil, nil, [])).to be_empty end it "infers undefined types for undefined links" do chain = described_class.new([Solargraph::Source::Chain::Link.new]) - expect(chain.infer(nil, nil, nil)).to be_undefined + expect(chain.infer(nil, nil, [])).to be_undefined end it "calls itself undefined if any of its links are undefined" do diff --git a/spec/source/change_spec.rb b/spec/source/change_spec.rb index 247c1e204..b9e019cfd 100644 --- a/spec/source/change_spec.rb +++ b/spec/source/change_spec.rb @@ -1,78 +1,80 @@ +# frozen_string_literal: true + describe Solargraph::Source::Change do - it "inserts a character" do + it 'inserts a character' do text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '.' - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.write(text) expect(updated).to eq('var.') end - it "repairs nullable characters" do + it 'repairs nullable characters' do text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '.' - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.write(text, true) expect(updated).to eq('var ') end - it "repairs entire changes" do + it 'repairs entire changes' do text = 'var' range = Solargraph::Range.from_to(0, 3, 0, 3) new_text = '._(!' - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.repair(text) expect(updated).to eq('var ') end - it "repairs nil ranges" do + it 'repairs nil ranges' do text = 'original' - change = Solargraph::Source::Change.new(nil, '...') + change = described_class.new(nil, '...') updated = change.repair(text) expect(updated).to eq(' ') end - it "overwrites nil ranges" do + it 'overwrites nil ranges' do text = 'foo' new_text = 'bar' - change = Solargraph::Source::Change.new(nil, new_text) + change = described_class.new(nil, new_text) updated = change.write(text) expect(updated).to eq('bar') end - it "blanks single colons in nullable changes" do + it 'blanks single colons in nullable changes' do text = 'bar' new_text = ':' range = Solargraph::Range.from_to(0, 3, 0, 3) - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.write(text, true) expect(updated).to eq('bar ') end - it "blanks double colons in nullable changes" do + it 'blanks double colons in nullable changes' do text = 'bar:' new_text = ':' range = Solargraph::Range.from_to(0, 4, 0, 4) - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.write(text, true) expect(updated).to eq('bar ') end - it "repairs preceding periods" do + it 'repairs preceding periods' do text = 'bar.' new_text = ' ' range = Solargraph::Range.from_to(0, 4, 0, 4) - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.repair(text) expect(updated).to eq('bar ') end - it "repairs preceding colons" do + it 'repairs preceding colons' do text = 'bar:' new_text = 'x' range = Solargraph::Range.from_to(0, 4, 0, 4) - change = Solargraph::Source::Change.new(range, new_text) + change = described_class.new(range, new_text) updated = change.repair(text) expect(updated).to eq('bar ') end diff --git a/spec/source/cursor_spec.rb b/spec/source/cursor_spec.rb index 150e99449..b9539c57a 100644 --- a/spec/source/cursor_spec.rb +++ b/spec/source/cursor_spec.rb @@ -1,5 +1,5 @@ describe Solargraph::Source::Cursor do - it "detects cursors in strings" do + it 'detects cursors in strings' do source = Solargraph::Source.load_string('str = "string"') cursor = described_class.new(source, Solargraph::Position.new(0, 6)) expect(cursor).not_to be_string @@ -7,7 +7,7 @@ expect(cursor).to be_string end - it "detects cursors in comments" do + it 'detects cursors in comments' do source = Solargraph::Source.load_string(%( # @type [String] var = make_a_string @@ -20,48 +20,48 @@ expect(cursor).not_to be_comment end - it "detects arguments inside parentheses" do + it 'detects arguments inside parentheses' do source = Solargraph::Source.load_string('a(1); b') - cur = described_class.new(source, Solargraph::Position.new(0,2)) + cur = described_class.new(source, Solargraph::Position.new(0, 2)) expect(cur).to be_argument - cur = described_class.new(source, Solargraph::Position.new(0,3)) + cur = described_class.new(source, Solargraph::Position.new(0, 3)) expect(cur).to be_argument - cur = described_class.new(source, Solargraph::Position.new(0,4)) + cur = described_class.new(source, Solargraph::Position.new(0, 4)) expect(cur).not_to be_argument - cur = described_class.new(source, Solargraph::Position.new(0,5)) + cur = described_class.new(source, Solargraph::Position.new(0, 5)) expect(cur).not_to be_argument - cur = described_class.new(source, Solargraph::Position.new(0,7)) + cur = described_class.new(source, Solargraph::Position.new(0, 7)) expect(cur).not_to be_argument end it 'detects arguments at opening parentheses' do source = Solargraph::Source.load_string('String.new', 'test.rb') - change = Solargraph::Source::Change.new(Solargraph::Range.from_to(0, 10, 0, 10) ,'(') + change = Solargraph::Source::Change.new(Solargraph::Range.from_to(0, 10, 0, 10), '(') updater = Solargraph::Source::Updater.new('test.rb', 1, [change]) source = source.synchronize(updater) cursor = source.cursor_at([0, 11]) expect(cursor).to be_argument end - it "detects class variables" do - source = double(:Source, :code => '@@foo') + it 'detects class variables' do + source = instance_double(Solargraph::Source, code: '@@foo') cur = described_class.new(source, Solargraph::Position.new(0, 2)) expect(cur.word).to eq('@@foo') end - it "detects instance variables" do - source = double(:Source, :code => '@foo') + it 'detects instance variables' do + source = instance_double(Solargraph::Source, code: '@foo') cur = described_class.new(source, Solargraph::Position.new(0, 1)) expect(cur.word).to eq('@foo') end - it "detects global variables" do - source = double(:Source, :code => '@foo') + it 'detects global variables' do + source = instance_double(Solargraph::Source, code: '$foo') cur = described_class.new(source, Solargraph::Position.new(0, 1)) - expect(cur.word).to eq('@foo') + expect(cur.word).to eq('$foo') end - it "generates word ranges" do + it 'generates word ranges' do source = Solargraph::Source.load_string(%( foo = bar )) @@ -69,26 +69,26 @@ expect(source.at(cur.range)).to eq('bar') end - it "generates chains" do + it 'generates chains' do source = Solargraph::Source.load_string('foo.bar(1,2).baz{}') cur = described_class.new(source, Solargraph::Position.new(0, 18)) expect(cur.chain).to be_a(Solargraph::Source::Chain) - expect(cur.chain.links.map(&:word)).to eq(['foo', 'bar', 'baz']) + expect(cur.chain.links.map(&:word)).to eq(%w[foo bar baz]) end - it "detects constant words" do - source = double(:Source, :code => 'Foo::Bar') + it 'detects constant words' do + source = instance_double(Solargraph::Source, code: 'Foo::Bar') cur = described_class.new(source, Solargraph::Position.new(0, 5)) expect(cur.word).to eq('Bar') end - it "detects cursors in dynamic strings" do + it 'detects cursors in dynamic strings' do source = Solargraph::Source.load_string('"#{100}"') cursor = source.cursor_at(Solargraph::Position.new(0, 7)) expect(cursor).to be_string end - it "detects cursors in embedded strings" do + it 'detects cursors in embedded strings' do source = Solargraph::Source.load_string('"#{100}..."') cursor = source.cursor_at(Solargraph::Position.new(0, 10)) expect(cursor).to be_string @@ -118,8 +118,8 @@ class Foo; end "#{[]}" ', 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, [ - Solargraph::Source::Change.new(Solargraph::Range.from_to(1, 12, 1, 12), '.') - ]) + Solargraph::Source::Change.new(Solargraph::Range.from_to(1, 12, 1, 12), '.') + ]) updated = source.synchronize(updater) cursor = updated.cursor_at(Solargraph::Position.new(1, 13)) expect(cursor).to be_string diff --git a/spec/source/source_chainer_spec.rb b/spec/source/source_chainer_spec.rb index 7a8eb9fb8..272cbb6ef 100644 --- a/spec/source/source_chainer_spec.rb +++ b/spec/source/source_chainer_spec.rb @@ -1,12 +1,14 @@ +# frozen_string_literal: true + describe Solargraph::Source::SourceChainer do - it "handles trailing colons that are not namespace separators" do + it 'handles trailing colons that are not namespace separators' do source = Solargraph::Source.load_string('Foo:') map = Solargraph::SourceMap.map(source) cursor = map.cursor_at(Solargraph::Position.new(0, 4)) expect(cursor.chain.links.first).to be_undefined end - it "recognizes literal strings" do + it 'recognizes literal strings' do map = Solargraph::SourceMap.load_string("'string'") cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain).not_to be_a(Solargraph::Source::Chain::Literal) @@ -15,8 +17,8 @@ expect(cursor.chain.links.first.word).to eq('<::String>') end - it "recognizes literal integers" do - map = Solargraph::SourceMap.load_string("100") + it 'recognizes literal integers' do + map = Solargraph::SourceMap.load_string('100') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain).not_to be_a(Solargraph::Source::Chain::Literal) cursor = map.cursor_at(Solargraph::Position.new(0, 1)) @@ -24,42 +26,42 @@ expect(cursor.chain.links.first.word).to eq('<::Integer>') end - it "recognizes literal regexps" do - map = Solargraph::SourceMap.load_string("/[a-z]/") + it 'recognizes literal regexps' do + map = Solargraph::SourceMap.load_string('/[a-z]/') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(cursor.chain.links.first.word).to eq('<::Regexp>') end - it "recognizes class variables" do + it 'recognizes class variables' do map = Solargraph::SourceMap.load_string('@@foo') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain.links.first).to be_a(Solargraph::Source::Chain::ClassVariable) expect(cursor.chain.links.first.word).to eq('@@foo') end - it "recognizes instance variables" do + it 'recognizes instance variables' do map = Solargraph::SourceMap.load_string('@foo') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain.links.first).to be_a(Solargraph::Source::Chain::InstanceVariable) expect(cursor.chain.links.first.word).to eq('@foo') end - it "recognizes global variables" do + it 'recognizes global variables' do map = Solargraph::SourceMap.load_string('$foo') cursor = map.cursor_at(Solargraph::Position.new(0, 0)) expect(cursor.chain.links.first).to be_a(Solargraph::Source::Chain::GlobalVariable) expect(cursor.chain.links.first.word).to eq('$foo') end - it "recognizes constants" do + it 'recognizes constants' do map = Solargraph::SourceMap.load_string('Foo::Bar') cursor = map.cursor_at(Solargraph::Position.new(0, 6)) expect(cursor.chain).to be_constant expect(cursor.chain.links.map(&:word)).to eq(['Foo::Bar']) end - it "recognizes unfinished constants" do + it 'recognizes unfinished constants' do map = Solargraph::SourceMap.load_string('Foo:: $something') cursor = map.cursor_at(Solargraph::Position.new(0, 5)) expect(cursor.chain).to be_constant @@ -67,11 +69,11 @@ expect(cursor.chain).to be_undefined end - it "recognizes unfinished calls" do + it 'recognizes unfinished calls' do orig = Solargraph::Source.load_string('foo.bar') updater = Solargraph::Source::Updater.new(nil, 1, [ - Solargraph::Source::Change.new(Solargraph::Range.from_to(0, 7, 0, 7), '.') - ]) + Solargraph::Source::Change.new(Solargraph::Range.from_to(0, 7, 0, 7), '.') + ]) source = orig.synchronize(updater) map = Solargraph::SourceMap.map(source) cursor = map.cursor_at(Solargraph::Position.new(0, 8)) @@ -80,25 +82,25 @@ expect(cursor.chain).to be_undefined end - it "chains signatures with square brackets" do + it 'chains signatures with square brackets' do map = Solargraph::SourceMap.load_string('foo[0].bar') cursor = map.cursor_at(Solargraph::Position.new(0, 8)) expect(cursor.chain.links.map(&:word)).to eq(['foo', '[]', 'bar']) end - it "chains signatures with curly brackets" do + it 'chains signatures with curly brackets' do map = Solargraph::SourceMap.load_string('foo{|x| x == y}.bar') cursor = map.cursor_at(Solargraph::Position.new(0, 16)) - expect(cursor.chain.links.map(&:word)).to eq(['foo', 'bar']) + expect(cursor.chain.links.map(&:word)).to eq(%w[foo bar]) end - it "chains signatures with parentheses" do + it 'chains signatures with parentheses' do map = Solargraph::SourceMap.load_string('foo(x, y).bar') cursor = map.cursor_at(Solargraph::Position.new(0, 10)) - expect(cursor.chain.links.map(&:word)).to eq(['foo', 'bar']) + expect(cursor.chain.links.map(&:word)).to eq(%w[foo bar]) end - it "chains from repaired sources with literal strings" do + it 'chains from repaired sources with literal strings' do orig = Solargraph::Source.load_string("''") updater = Solargraph::Source::Updater.new( nil, @@ -111,118 +113,116 @@ ] ) source = orig.synchronize(updater) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 3)) + chain = described_class.chain(source, Solargraph::Position.new(0, 3)) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(chain.links.length).to eq(2) end - it "chains incomplete constants" do - source = Solargraph::Source.load_string("Foo::") - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 5)) + it 'chains incomplete constants' do + source = Solargraph::Source.load_string('Foo::') + chain = described_class.chain(source, Solargraph::Position.new(0, 5)) expect(chain.links.length).to eq(2) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Constant) expect(chain.links.last).to be_a(Solargraph::Source::Chain::Constant) expect(chain.links.last).to be_undefined end - it "works when source error ranges contain a nil range" do + it 'works when source error ranges contain a nil range' do orig = Solargraph::Source.load_string("msg = 'msg'\nmsg", 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, [ - Solargraph::Source::Change.new(nil, "msg = 'msg'\nmsg.") - ]) + Solargraph::Source::Change.new(nil, "msg = 'msg'\nmsg.") + ]) source = orig.synchronize(updater) - expect { - Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 4)) - }.not_to raise_error + expect do + described_class.chain(source, Solargraph::Position.new(1, 4)) + end.not_to raise_error end - it "stops phrases at opening brackets" do + it 'stops phrases at opening brackets' do source = Solargraph::Source.load_string(%( (aa1, 2, 3) [bb2, 2, 3] {cc3, 2, 3} )) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 10)) + chain = described_class.chain(source, Solargraph::Position.new(1, 10)) expect(chain.links.first.word).to eq('aa1') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(2, 10)) + chain = described_class.chain(source, Solargraph::Position.new(2, 10)) expect(chain.links.first.word).to eq('bb2') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(3, 10)) + chain = described_class.chain(source, Solargraph::Position.new(3, 10)) expect(chain.links.first.word).to eq('cc3') end - it "chains instance variables from unsynchronized sources" do - source = double(Solargraph::Source, - :synchronized? => false, - :code => '@foo.', - :filename => 'test.rb', - :string_at? => false, - :comment_at? => false, - :repaired? => false, - :parsed? => true, - :error_ranges => [], - :node_at => nil, - :tree_at => [] - ) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 5)) + it 'chains instance variables from unsynchronized sources' do + source = instance_double(Solargraph::Source, + synchronized?: false, + code: '@foo.', + filename: 'test.rb', + string_at?: false, + comment_at?: false, + repaired?: false, + parsed?: true, + error_ranges: [], + node_at: nil, + tree_at: []) + chain = described_class.chain(source, Solargraph::Position.new(0, 5)) expect(chain.links.first.word).to eq('@foo') expect(chain.links.last.word).to eq('') end - it "chains class variables from unsynchronized sources" do - source = double(Solargraph::Source, - :synchronized? => false, - :code => '@@foo.', - :filename => 'test.rb', - :string_at? => false, - :comment_at? => false, - :repaired? => false, - :parsed? => true, - :error_ranges => [], - :node_at => nil, - :tree_at => [] - ) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 6)) + it 'chains class variables from unsynchronized sources' do + source = instance_double(Solargraph::Source, + synchronized?: false, + code: '@@foo.', + filename: 'test.rb', + string_at?: false, + comment_at?: false, + repaired?: false, + parsed?: true, + error_ranges: [], + node_at: nil, + tree_at: []) + chain = described_class.chain(source, Solargraph::Position.new(0, 6)) expect(chain.links.first.word).to eq('@@foo') expect(chain.links.last.word).to eq('') end - it "detects literals from chains in unsynchronized sources" do + it 'detects literals from chains in unsynchronized sources' do source1 = Solargraph::Source.load_string(%( '' )) source2 = source1.synchronize(Solargraph::Source::Updater.new( - nil, - 2, - [ - Solargraph::Source::Change.new( - Solargraph::Range.from_to(1, 8, 1, 8), - '.' - ) - ] - )) - chain = Solargraph::Source::SourceChainer.chain(source2, Solargraph::Position.new(1, 9)) + nil, + 2, + [ + Solargraph::Source::Change.new( + Solargraph::Range.from_to(1, 8, 1, 8), + '.' + ) + ] + )) + chain = described_class.chain(source2, Solargraph::Position.new(1, 9)) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(chain.links.first.word).to eq('<::String>') expect(chain.links.last.word).to eq('') end - it "ignores ? and ! that are not method suffixes" do + it 'ignores ? and ! that are not method suffixes' do source = Solargraph::Source.load_string(%( if !t ), 'test.rb') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 11)) + chain = described_class.chain(source, Solargraph::Position.new(1, 11)) expect(chain.links.length).to eq(1) expect(chain.links.first.word).to eq('t') end - it "chains from fixed phrases in repaired sources with missing nodes" do + it 'chains from fixed phrases in repaired sources with missing nodes' do source = Solargraph::Source.load_string(%( x = [] ), 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, [ - Solargraph::Source::Change.new(Solargraph::Range.from_to(2, 6, 2, 6), 'x.') - ]) + Solargraph::Source::Change.new(Solargraph::Range.from_to(2, 6, 2, 6), 'x.') + ]) updated = source.synchronize(updater) cursor = updated.cursor_at(Solargraph::Position.new(2, 8)) expect(cursor.chain.links.first.word).to eq('x') @@ -230,13 +230,13 @@ it 'handles integers with dots at end of file' do source = Solargraph::Source.load_string('100.') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(0, 4)) + chain = described_class.chain(source, Solargraph::Position.new(0, 4)) expect(chain.links.length).to eq(2) expect(chain.links.first).to be_a(Solargraph::Source::Chain::Literal) expect(chain.links.last).to be_undefined end - it 'detects whole constant with cursor at double colon' do + it 'detects whole constant with cursor at double colon - double level' do source = Solargraph::Source.load_string(%( class Outer class Inner @@ -244,11 +244,11 @@ class Inner end Outer::Inner.new ), 'test.rb') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(5, 13)) + chain = described_class.chain(source, Solargraph::Position.new(5, 13)) expect(chain.links.last.word).to eq('Outer::Inner') end - it 'detects whole constant with cursor at double colon' do + it 'detects whole constant with cursor at double colon - triple level' do source = Solargraph::Source.load_string(%( class Outer class Inner1 @@ -258,7 +258,7 @@ class Inner2 end Outer::Inner1::Inner2.new ), 'test.rb') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(7, 21)) + chain = described_class.chain(source, Solargraph::Position.new(7, 21)) expect(chain.links.last.word).to eq('Outer::Inner1::Inner2') end @@ -266,7 +266,7 @@ class Inner2 source = Solargraph::Source.load_string(%( foo(*optargs, **kwargs) ), 'test.rb') - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 7)) + chain = described_class.chain(source, Solargraph::Position.new(1, 7)) expect(chain.links.last.arguments.length).to eq(2) end @@ -278,7 +278,7 @@ class Inner2 api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(2, 9)) + chain = described_class.chain(source, Solargraph::Position.new(2, 9)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Array') end @@ -290,12 +290,12 @@ class Inner2 api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 16)) + chain = described_class.chain(source, Solargraph::Position.new(1, 16)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Array(String, Integer)') end - it 'infers tuple type when types in literal differ' do + it 'allows Array methods when tuple type in literal inferred' do source = Solargraph::Source.load_string(%( b = ['a', 'b', 123] c = b.include?('a') @@ -304,7 +304,7 @@ class Inner2 api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(3, 6)) + chain = described_class.chain(source, Solargraph::Position.new(3, 6)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.rooted_tag).to eq('::Boolean') end @@ -316,7 +316,7 @@ class Inner2 api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 16)) + chain = described_class.chain(source, Solargraph::Position.new(1, 16)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Array') end @@ -331,12 +331,12 @@ def strings; end end ), 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, [ - Solargraph::Source::Change.new(Solargraph::Range.from_to(5, 10, 5, 10), 'if s') - ]) + Solargraph::Source::Change.new(Solargraph::Range.from_to(5, 10, 5, 10), 'if s') + ]) updated = source.synchronize(updater) api_map = Solargraph::ApiMap.new api_map.map updated - chain = Solargraph::Source::SourceChainer.chain(updated, Solargraph::Position.new(5, 14)) + chain = described_class.chain(updated, Solargraph::Position.new(5, 14)) expect(chain.node.type).to be(:send) expect(chain.node.children[1]).to be(:s) end @@ -347,8 +347,9 @@ def strings; end z end )) - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 9)) - expect(chain.links.map(&:class)).to be + chain = described_class.chain(source, Solargraph::Position.new(1, 9)) + expect(chain.links.map(&:class)) + .to eq([Solargraph::Source::Chain::Call, Solargraph::Source::Chain::Call]) end it 'infers specific array type from block sent to Array#map' do @@ -358,7 +359,7 @@ def strings; end api_map = Solargraph::ApiMap.new api_map.map source - chain = Solargraph::Source::SourceChainer.chain(source, Solargraph::Position.new(1, 20)) + chain = described_class.chain(source, Solargraph::Position.new(1, 20)) type = chain.infer(api_map, Solargraph::Pin::ROOT_PIN, api_map.source_map('test.rb').locals) expect(type.tag).to eq('Array') end diff --git a/spec/source/updater_spec.rb b/spec/source/updater_spec.rb index 0c5f1f4c6..373072e0d 100644 --- a/spec/source/updater_spec.rb +++ b/spec/source/updater_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::Source::Updater do - it "applies changes" do + it 'applies changes' do text = 'foo' changes = [] range = Solargraph::Range.from_to(0, 3, 0, 3) @@ -8,12 +10,12 @@ range = Solargraph::Range.from_to(0, 4, 0, 4) new_text = 'bar' changes.push Solargraph::Source::Change.new(range, new_text) - updater = Solargraph::Source::Updater.new('file.rb', 0, changes) + updater = described_class.new('file.rb', 0, changes) updated = updater.write(text) expect(updated).to eq('foo.bar') end - it "applies repairs" do + it 'applies repairs' do text = 'foo' changes = [] range = Solargraph::Range.from_to(0, 3, 0, 3) @@ -22,18 +24,18 @@ range = Solargraph::Range.from_to(0, 4, 0, 4) new_text = 'bar' changes.push Solargraph::Source::Change.new(range, new_text) - updater = Solargraph::Source::Updater.new('file.rb', 0, changes) + updater = described_class.new('file.rb', 0, changes) updated = updater.repair(text) expect(updated).to eq('foo ') end - it "handles nil ranges" do + it 'handles nil ranges' do text = 'foo' changes = [] range = nil new_text = 'bar' changes.push Solargraph::Source::Change.new(range, new_text) - updater = Solargraph::Source::Updater.new('file.rb', 0, changes) + updater = described_class.new('file.rb', 0, changes) updated = updater.write(text) expect(updated).to eq('bar') end diff --git a/spec/source_map/clip_spec.rb b/spec/source_map/clip_spec.rb index 1afa3f12c..67b3085b2 100644 --- a/spec/source_map/clip_spec.rb +++ b/spec/source_map/clip_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + describe Solargraph::SourceMap::Clip do let(:api_map) { Solargraph::ApiMap.new } @@ -93,7 +95,7 @@ class Bar api_map = Solargraph::ApiMap.new api_map.map source clip = api_map.clip_at('test.rb', [6, 12]) - expect(clip.complete.pins.map(&:name)).to eq (['@foo']) + expect(clip.complete.pins.map(&:name)).to eq(['@foo']) end it 'completes instance variables' do @@ -288,7 +290,7 @@ def bar klass expect(clip.infer.tag).to eq('String') end - it 'infers method types from return nodes' do + it 'infers method types from return nodes - initialization' do source = Solargraph::Source.load_string(%( def foo String.new(from_object) @@ -302,7 +304,7 @@ def foo expect(type.tag).to eq('String') end - it 'infers method types from return nodes' do + it 'infers method types from return nodes - method return type' do source = Solargraph::Source.load_string(%( class Foo # @return [self] @@ -356,7 +358,7 @@ def spec_for_require path map.map source clip = map.clip_at('test.rb', Solargraph::Position.new(10, 10)) type = clip.infer - expect(type.to_s.split(',').map(&:strip).to_set).to eq(Set.new(['Gem::Specification'])) + expect(type.to_s.split(',').to_set(&:strip)).to eq(Set.new(['Gem::Specification'])) end it 'infers return types from method calls' do @@ -667,7 +669,7 @@ def initialize clip = api_map.clip_at('test.rb', [7, 8]) # @todo expect(clip.infer.tags).to eq('""') expect(clip.infer.tags).to eq('String') - expect(clip.infer.simple_tags).to eq("String") + expect(clip.infer.simple_tags).to eq('String') end it 'completes instance variable methods in rebound blocks' do @@ -1660,7 +1662,7 @@ def foo; end api_map = Solargraph::ApiMap.new.map(source) array_names = api_map.clip_at('test.rb', [5, 22]).complete.pins.map(&:name) - expect(array_names).to eq(["byteindex", "byterindex", "bytes", "bytesize", "byteslice", "bytesplice"]) + expect(array_names).to eq(%w[byteindex byterindex bytes bytesize byteslice bytesplice]) string_names = api_map.clip_at('test.rb', [6, 22]).complete.pins.map(&:name) # can be brought in by solargraph-rails @@ -1883,17 +1885,6 @@ def bad_passthrough; yield; end expect(type.to_s).to eq('undefined') end - it 'infers block-pass symbols from generics' do - source = Solargraph::Source.load_string(%( - array = [0, 1, 2] - array.max_by(&:abs) - ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [2, 13]) - type = clip.infer - expect(type.to_s).to eq('Integer, nil') - end - it 'infers block-pass symbols with variant yields' do source = Solargraph::Source.load_string(%( array = [0] @@ -2301,17 +2292,6 @@ def meth arg, arg2 expect(type.to_s).to eq('Array(String, Integer)') end - it 'infers array of identical diverse arrays into tuples' do - source = Solargraph::Source.load_string(%( - h = [['foo', 1], ['bar', 2]] - h - ), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [2, 6]) - type = clip.infer - expect(type.to_s).to eq('Array') - end - it 'infers literal diverse array of diverse arrays into tuple of tuples' do source = Solargraph::Source.load_string(%( h = [['foo', 1], ['bar', :baz]] @@ -2490,101 +2470,6 @@ def [](index); end expect(clip.infer.to_s).to eq('Float') end - it 'can use strings and symbols to choose a signature' do - source = Solargraph::Source.load_string(%( - # @generic A - # @generic B - class Foo - # @overload find(index) - # @param [String] index - # @return [generic] - # @overload find(index) - # @param [Symbol] index - # @return [generic] - def find(index); end - end - - # @type [Foo(String, Integer)] - m = blah - mb = m.find('foo') - mb - mc = m.find(:bar) - mc -), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [16, 6]) - expect(clip.infer.to_s).to eq('String') - - clip = api_map.clip_at('test.rb', [18, 6]) - expect(clip.infer.to_s).to eq('Integer') - end - - it 'uses types to determine overload of [] to match' do - source = Solargraph::Source.load_string(%( - # @generic A - # @generic B - class Foo - # @overload [](index) - # @param [String] index - # @return [generic] - # @overload [](index) - # @param [Symbol] index - # @return [generic] - def [](index); end - end - - # @type [Foo(String, Integer)] - m = blah - mb = m['foo'] - mb - mc = m[:bar] - mc -), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [16, 6]) - expect(clip.infer.to_s).to eq('String') - - clip = api_map.clip_at('test.rb', [18, 6]) - expect(clip.infer.to_s).to eq('Integer') - end - - it 'uses literal types to determine overload of [] to match' do - source = Solargraph::Source.load_string(%( - # @generic A - # @generic B - class Foo - # @overload [](index) - # @param [1] index - # @return [generic] - # @overload [](index) - # @param [2] index - # @return [generic] - # @overload [](index) - # @param [Integer] index - # @return [Float] - def [](index); end - end - - # @type [Foo(String, Integer)] - m = blah - mb = m[1] - mb - mc = m[2] - mc - md = m[3] - md -), 'test.rb') - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [19, 6]) - expect(clip.infer.to_s).to eq('String') - - clip = api_map.clip_at('test.rb', [21, 6]) - expect(clip.infer.to_s).to eq('Integer') - - clip = api_map.clip_at('test.rb', [23, 6]) - expect(clip.infer.to_s).to eq('Float') - end - it 'interprets self type in superclass method return type' do source = Solargraph::Source.load_string(%( class Foo @@ -2753,7 +2638,7 @@ def bar; end api_map = Solargraph::ApiMap.new.map(source) clip = api_map.clip_at('test.rb', [7, 6]) # The order of the types can vary between platforms - expect(clip.infer.items.map(&:to_s).sort).to eq(["123", ":foo", "String"]) + expect(clip.infer.items.map(&:to_s).sort).to eq(['123', ':foo', 'String']) end it 'does not map Module methods into an Object' do @@ -3016,21 +2901,6 @@ def foo a expect(clip.infer.to_s).to eq('Array') end - it 'preserves hash value when it is a union with brackets' do - pending 'union in bracket support' - - source = Solargraph::Source.load_string(%( - # @type [Hash{String => [Array, Hash, Integer, nil]}] - raw_data = {} - a = raw_data['domains'] - a - ), 'test.rb') - - api_map = Solargraph::ApiMap.new.map(source) - clip = api_map.clip_at('test.rb', [4, 6]) - expect(clip.infer.to_s).to eq('Array, Hash, Integer, nil') - end - it 'handles block method super scenarios' do source = Solargraph::Source.load_string(%( class Foo @@ -3312,7 +3182,7 @@ def foo expect(names).not_to include('bar=', 'baz=') end - it 'completes Struct methods via const assignment without a block' do + it 'completes Data methods via const assignment without a block' do source = Solargraph::Source.load_string(%( # @param bar [String] # @param baz [Integer] diff --git a/spec/source_map/mapper_spec.rb b/spec/source_map/mapper_spec.rb index 96d2bdab5..02cd17e29 100644 --- a/spec/source_map/mapper_spec.rb +++ b/spec/source_map/mapper_spec.rb @@ -1,5 +1,7 @@ +# frozen_string_literal: true + describe Solargraph::SourceMap::Mapper do - it "ignores include calls that are not attached to the current namespace" do + it 'ignores include calls that are not attached to the current namespace' do source = Solargraph::Source.new(%( class Foo include Direct @@ -8,14 +10,14 @@ class Foo end )) map = Solargraph::SourceMap.map(source) - pins = map.pins.select{|pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.namespace == 'Foo'} + pins = map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Include) && pin.namespace == 'Foo' } names = pins.map(&:name) expect(names).to include('Direct') expect(names).not_to include('Indirect') expect(names).to include('Interior') end - it "ignores prepend calls that are not attached to the current namespace" do + it 'ignores prepend calls that are not attached to the current namespace' do source = Solargraph::Source.new(%( class Foo prepend Direct @@ -24,14 +26,14 @@ class Foo end )) map = Solargraph::SourceMap.map(source) - pins = map.pins.select{|pin| pin.is_a?(Solargraph::Pin::Reference::Prepend) && pin.namespace == 'Foo'} + pins = map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Prepend) && pin.namespace == 'Foo' } names = pins.map(&:name) expect(names).to include('Direct') expect(names).not_to include('Indirect') expect(names).to include('Interior') end - it "ignores extend calls that are not attached to the current namespace" do + it 'ignores extend calls that are not attached to the current namespace' do source = Solargraph::Source.new(%( class Foo extend Direct @@ -40,17 +42,17 @@ class Foo end )) map = Solargraph::SourceMap.map(source) - foo_pin = map.pins.select{|pin| pin.path == 'Foo'}.first + map.pins.select { |pin| pin.path == 'Foo' }.first # expect(foo_pin.extend_references.map(&:name)).to include('Direct') # expect(foo_pin.extend_references.map(&:name)).not_to include('Indirect') - pins = map.pins.select{|pin| pin.is_a?(Solargraph::Pin::Reference::Extend) && pin.namespace == 'Foo'} + pins = map.pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Extend) && pin.namespace == 'Foo' } names = pins.map(&:name) expect(names).to include('Direct') expect(names).not_to include('Indirect') expect(names).to include('Interior') end - it "sets scopes for attributes" do + it 'sets scopes for attributes' do source = Solargraph::Source.new(%( module Foo attr_reader :bar1 @@ -60,13 +62,13 @@ class << self end )) map = Solargraph::SourceMap.map(source) - bar1 = map.pins.select{|pin| pin.name == 'bar1'}.first + bar1 = map.pins.select { |pin| pin.name == 'bar1' }.first expect(bar1.scope).to eq(:instance) - bar2 = map.pins.select{|pin| pin.name == 'bar2'}.first + bar2 = map.pins.select { |pin| pin.name == 'bar2' }.first expect(bar2.scope).to eq(:class) end - it "sets attribute visibility" do + it 'sets attribute visibility' do map = Solargraph::SourceMap.load_string(%( module Foo attr_reader :default_public_method @@ -84,7 +86,7 @@ module Foo expect(map.first_pin('Foo#explicit_public_method').visibility).to eq(:public) end - it "processes method directives" do + it 'processes method directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!method bar(baz) @@ -119,7 +121,7 @@ class Foo expect(pin.scope).to eq(:class) end - it "processes attribute reader directives" do + it 'processes attribute reader directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [r] bar @@ -131,7 +133,7 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "processes attribute writer directives" do + it 'processes attribute writer directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [w] bar @@ -143,7 +145,7 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "processes attribute accessor directives" do + it 'processes attribute accessor directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [r,w] bar @@ -157,7 +159,7 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "processes default attribute directives" do + it 'processes default attribute directives' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute bar @@ -171,7 +173,7 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "processes attribute directives attached to methods" do + it 'processes attribute directives attached to methods' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [r] bar @@ -184,7 +186,7 @@ def make_bar_attr expect(pin.return_type.tag).to eq('String') end - it "processes private visibility directives attached to methods" do + it 'processes private visibility directives attached to methods' do map = Solargraph::SourceMap.load_string(%( class Foo # @!visibility private @@ -195,7 +197,7 @@ def bar expect(map.first_pin('Foo#bar').visibility).to be(:private) end - it "processes protected visibility directives attached to methods" do + it 'processes protected visibility directives attached to methods' do map = Solargraph::SourceMap.load_string(%( class Foo # @!visibility protected @@ -206,7 +208,7 @@ def bar expect(map.first_pin('Foo#bar').visibility).to be(:protected) end - it "processes public visibility directives attached to methods" do + it 'processes public visibility directives attached to methods' do map = Solargraph::SourceMap.load_string(%( class Foo # @!visibility public @@ -217,7 +219,7 @@ def bar expect(map.first_pin('Foo#bar').visibility).to be(:public) end - it "does not process attached visibility directives on other methods" do + it 'does not process attached visibility directives on other methods' do map = Solargraph::SourceMap.load_string(%( class Example # @!visibility private @@ -232,7 +234,7 @@ def method2; end expect(method2.visibility).to be(:public) end - it "processes class-wide private visibility directives" do + it 'processes class-wide private visibility directives' do map = Solargraph::SourceMap.load_string(%( class Example # @!visibility private @@ -253,7 +255,7 @@ def method3; end expect(method3.visibility).to be(:public) end - it "processes attribute directives at class endings" do + it 'processes attribute directives at class endings' do map = Solargraph::SourceMap.load_string(%( class Foo # @!attribute [r] bar @@ -264,43 +266,43 @@ class Foo expect(pin.return_type.tag).to eq('String') end - it "finds assignment nodes for local variables using nil guards" do + it 'finds assignment nodes for local variables using nil guards' do map = Solargraph::SourceMap.load_string(%( x ||= [] )) pin = map.locals.first # @todo Dirty test - expect([:ZLIST, :ZARRAY, :array]).to include(pin.assignment.type) + expect(%i[ZLIST ZARRAY array]).to include(pin.assignment.type) end - it "finds assignment nodes for instance variables using nil guards" do + it 'finds assignment nodes for instance variables using nil guards' do map = Solargraph::SourceMap.load_string(%( @x ||= [] )) pin = map.pins.last # @todo Dirty test - expect([:ZLIST, :ZARRAY, :array]).to include(pin.assignment.type) + expect(%i[ZLIST ZARRAY array]).to include(pin.assignment.type) end - it "finds assignment nodes for class variables using nil guards" do + it 'finds assignment nodes for class variables using nil guards' do map = Solargraph::SourceMap.load_string(%( @@x ||= [] )) pin = map.pins.last # @todo Dirty test - expect([:ZLIST, :ZARRAY, :array]).to include(pin.assignment.type) + expect(%i[ZLIST ZARRAY array]).to include(pin.assignment.type) end - it "finds assignment nodes for global variables using nil guards" do + it 'finds assignment nodes for global variables using nil guards' do map = Solargraph::SourceMap.load_string(%( $x ||= [] )) pin = map.pins.last # @todo Dirty test - expect([:ZLIST, :ZARRAY, :array]).to include(pin.assignment.type) + expect(%i[ZLIST ZARRAY array]).to include(pin.assignment.type) end - it "requalifies namespace definitions with leading colons" do + it 'requalifies namespace definitions with leading colons' do map = Solargraph::SourceMap.load_string(%( class Foo class ::Bar; end @@ -311,7 +313,7 @@ class ::Bar; end expect(map.pins.map(&:path)).not_to include('Foo::Bar') end - it "maps method parameters" do + it 'maps method parameters' do map = Solargraph::SourceMap.load_string(%( class Foo def bar baz, boo = 'boo', key: 'value' @@ -319,16 +321,16 @@ def bar baz, boo = 'boo', key: 'value' end )) pin = map.first_pin('Foo#bar') - expect(pin.parameter_names).to eq(['baz', 'boo', 'key']) - pin = map.locals.select{|p| p.name == 'baz'}.first + expect(pin.parameter_names).to eq(%w[baz boo key]) + pin = map.locals.select { |p| p.name == 'baz' }.first expect(pin).to be_a(Solargraph::Pin::Parameter) - pin = map.locals.select{|p| p.name == 'boo'}.first + pin = map.locals.select { |p| p.name == 'boo' }.first expect(pin).to be_a(Solargraph::Pin::Parameter) - pin = map.locals.select{|p| p.name == 'key'}.first + pin = map.locals.select { |p| p.name == 'key' }.first expect(pin).to be_a(Solargraph::Pin::Parameter) end - it "maps method splat parameters" do + it 'maps method splat parameters' do map = Solargraph::SourceMap.load_string(%( class Foo def bar *baz @@ -340,7 +342,7 @@ def bar *baz expect(pin.parameters.first.name).to eq('baz') end - it "maps method block parameters" do + it 'maps method block parameters' do map = Solargraph::SourceMap.load_string(%( class Foo def bar &block @@ -352,18 +354,18 @@ def bar &block expect(pin.parameters.first.name).to eq('block') end - it "adds superclasses to class pins" do + it 'adds superclasses to class pins' do map = Solargraph::SourceMap.load_string(%( class Sub < Sup; end )) # pin = map.first_pin('Sub') # expect(pin.superclass_reference.name).to eq('Sup') - pin = map.pins.select{|p| p.is_a?(Solargraph::Pin::Reference::Superclass)}.first + pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::Reference::Superclass) }.first expect(pin.namespace).to eq('Sub') expect(pin.name).to eq('Sup') end - it "modifies scope and visibility for module functions" do + it 'modifies scope and visibility for module functions' do map = Solargraph::SourceMap.load_string(%( module Functions module_function @@ -376,7 +378,7 @@ def foo; end expect(pin.visibility).to eq(:private) end - it "recognizes single module functions" do + it 'recognizes single module functions' do map = Solargraph::SourceMap.load_string(%( module Functions module_function def foo; end @@ -391,7 +393,7 @@ def bar; end expect(pin.visibility).to eq(:public) end - it "remaps methods for module_function symbol arguments" do + it 'remaps methods for module_function symbol arguments' do map = Solargraph::SourceMap.load_string(%( module Functions def foo @@ -409,13 +411,13 @@ def bar expect(pin.visibility).to eq(:private) pin = map.first_pin('Functions#bar') expect(pin.visibility).to eq(:public) - pin = map.pins.select{|p| p.name == '@foo' and p.context.scope == :class}.first + pin = map.pins.select { |p| p.name == '@foo' and p.context.scope == :class }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) - pin = map.pins.select{|p| p.name == '@foo' and p.context.scope == :instance}.first + pin = map.pins.select { |p| p.name == '@foo' and p.context.scope == :instance }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) end - it "modifies instance variables in module functions" do + it 'modifies instance variables in module functions' do map = Solargraph::SourceMap.load_string(%( module Functions module_function @@ -425,46 +427,46 @@ def foo end end )) - pin = map.pins.select{|p| p.name == '@foo' and p.context.scope == :class}.first + pin = map.pins.select { |p| p.name == '@foo' and p.context.scope == :class }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) - pin = map.pins.select{|p| p.name == '@foo' and p.context.scope == :instance}.first + pin = map.pins.select { |p| p.name == '@foo' and p.context.scope == :instance }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) - pin = map.pins.select{|p| p.name == '@bar' and p.context.scope == :class}.first + pin = map.pins.select { |p| p.name == '@bar' and p.context.scope == :class }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) - pin = map.pins.select{|p| p.name == '@bar' and p.context.scope == :instance}.first + pin = map.pins.select { |p| p.name == '@bar' and p.context.scope == :instance }.first expect(pin).to be_a(Solargraph::Pin::InstanceVariable) end - it "maps class variables" do + it 'maps class variables' do map = Solargraph::SourceMap.load_string(%( class Foo @@bar = 'bar' @@baz ||= 'baz' end )) - pin = map.pins.select{|p| p.name == '@@bar'}.first + pin = map.pins.select { |p| p.name == '@@bar' }.first expect(pin).to be_a(Solargraph::Pin::ClassVariable) - pin = map.pins.select{|p| p.name == '@@baz'}.first + pin = map.pins.select { |p| p.name == '@@baz' }.first expect(pin).to be_a(Solargraph::Pin::ClassVariable) end - it "maps local variables" do + it 'maps local variables' do map = Solargraph::SourceMap.load_string(%( x = y )) - pin = map.locals.select{|p| p.name == 'x'}.first + pin = map.locals.select { |p| p.name == 'x' }.first expect(pin).to be_a(Solargraph::Pin::LocalVariable) end - it "maps global variables" do + it 'maps global variables' do map = Solargraph::SourceMap.load_string(%( $x = y )) - pin = map.pins.select{|p| p.name == '$x'}.first + pin = map.pins.select { |p| p.name == '$x' }.first expect(pin).to be_a(Solargraph::Pin::GlobalVariable) end - it "maps constants" do + it 'maps constants' do map = Solargraph::SourceMap.load_string(%( module Foo BAR = 'bar' @@ -474,7 +476,7 @@ module Foo expect(pin).to be_a(Solargraph::Pin::Constant) end - it "maps singleton methods" do + it 'maps singleton methods' do map = Solargraph::SourceMap.load_string(%( class Foo def self.bar; end @@ -485,7 +487,7 @@ def self.bar; end expect(pin.context.scope).to be(:class) end - it "maps requalified singleton methods" do + it 'maps requalified singleton methods' do map = Solargraph::SourceMap.load_string(%( class Foo; end class Bar @@ -505,7 +507,7 @@ def boo; end expect(pin.context.scope).to be(:instance) end - it "maps private class methods" do + it 'maps private class methods' do map = Solargraph::SourceMap.load_string(%( class Foo def self.bar; end @@ -517,7 +519,7 @@ def self.bar; end expect(pin.visibility).to be(:private) end - it "maps singly defined private class methods" do + it 'maps singly defined private class methods' do map = Solargraph::SourceMap.load_string(%( class Foo private_class_method def bar; end @@ -528,7 +530,7 @@ class Foo expect(pin.visibility).to be(:private) end - it "maps private constants" do + it 'maps private constants' do map = Solargraph::SourceMap.load_string(%( class Foo BAR = 'bar' @@ -540,7 +542,7 @@ class Foo expect(pin.visibility).to be(:private) end - it "maps private namespaces" do + it 'maps private namespaces' do map = Solargraph::SourceMap.load_string(%( class Foo class Bar; end @@ -552,7 +554,7 @@ class Bar; end expect(pin.visibility).to be(:private) end - it "maps attribute writers" do + it 'maps attribute writers' do map = Solargraph::SourceMap.load_string(%( class Foo attr_writer :bar @@ -562,7 +564,7 @@ class Foo expect(map.pins.map(&:path)).not_to include('Foo#bar') end - it "maps attribute accessors" do + it 'maps attribute accessors' do map = Solargraph::SourceMap.load_string(%( class Foo attr_accessor :bar @@ -572,29 +574,29 @@ class Foo expect(map.pins.map(&:path)).to include('Foo#bar') end - it "maps extend self" do + it 'maps extend self' do map = Solargraph::SourceMap.load_string(%( class Foo extend self def bar; end end )) - pin = map.first_pin('Foo') + map.first_pin('Foo') # expect(pin.extend_references.map(&:name)).to include('Foo') - pin = map.pins.select{|p| p.is_a?(Solargraph::Pin::Reference::Extend)}.first + pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::Reference::Extend) }.first expect(pin.namespace).to eq('Foo') expect(pin.name).to eq('Foo') end - it "maps require calls" do + it 'maps require calls' do map = Solargraph::SourceMap.load_string(%( require 'set' )) - pin = map.pins.select{|p| p.is_a?(Solargraph::Pin::Reference::Require)}.first + pin = map.pins.select { |p| p.is_a?(Solargraph::Pin::Reference::Require) }.first expect(pin.name).to eq('set') end - it "ignores dynamic require calls" do + it 'ignores dynamic require calls' do map = Solargraph::SourceMap.load_string(%( path = 'solargraph' require path @@ -602,16 +604,16 @@ def bar; end expect(map.requires.length).to eq(0) end - it "maps block parameters" do + it 'maps block parameters' do map = Solargraph::SourceMap.load_string(%( x.each do |y| end )) - pin = map.locals.select{|p| p.name == 'y'}.first + pin = map.locals.select { |p| p.name == 'y' }.first expect(pin).to be_a(Solargraph::Pin::Parameter) end - it "forces initialize methods to be private" do + it 'forces initialize methods to be private' do map = Solargraph::SourceMap.load_string(' class Foo def initialize name @@ -622,7 +624,7 @@ def initialize name expect(pin.visibility).to be(:private) end - it "maps top-level methods" do + it 'maps top-level methods' do map = Solargraph::SourceMap.load_string(%( def foo(bar, baz) end @@ -632,18 +634,18 @@ def foo(bar, baz) expect(pin).to be_a(Solargraph::Pin::Method) end - it "maps root blocks to class scope" do + it 'maps root blocks to class scope' do smap = Solargraph::SourceMap.load_string(%( @a = some_array @a.each do |b| b end ), 'test.rb') - pin = smap.pins.select{|p| p.is_a?(Solargraph::Pin::Block)}.first + pin = smap.pins.select { |p| p.is_a?(Solargraph::Pin::Block) }.first expect(pin.context.scope).to eq(:class) end - it "maps class method blocks to class scope" do + it 'maps class method blocks to class scope' do smap = Solargraph::SourceMap.load_string(%( class Foo def self.bar @@ -654,11 +656,11 @@ def self.bar end end )) - pin = smap.pins.select{|p| p.is_a?(Solargraph::Pin::Block)}.first + pin = smap.pins.select { |p| p.is_a?(Solargraph::Pin::Block) }.first expect(pin.context.scope).to eq(:class) end - it "maps instance method blocks to instance scope" do + it 'maps instance method blocks to instance scope' do smap = Solargraph::SourceMap.load_string(%( class Foo def bar @@ -669,11 +671,11 @@ def bar end end )) - pin = smap.pins.select{|p| p.is_a?(Solargraph::Pin::Block)}.first + pin = smap.pins.select { |p| p.is_a?(Solargraph::Pin::Block) }.first expect(pin.context.scope).to eq(:instance) end - it "maps rebased namespaces without leading colons" do + it 'maps rebased namespaces without leading colons' do smap = Solargraph::SourceMap.load_string(%( class Foo class ::Bar @@ -686,79 +688,79 @@ def baz; end expect(smap.first_pin('Bar#baz')).to be_a(Solargraph::Pin::Method) end - it "maps contexts of constants" do + it 'maps contexts of constants' do var = 'BAR' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps contexts of instance variables" do + it 'maps contexts of instance variables' do var = '@bar' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps contexts of class variables" do + it 'maps contexts of class variables' do var = '@@bar' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps contexts of global variables" do + it 'maps contexts of global variables' do var = '$bar' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.pins.select{|p| p.name == var}.first + pin = smap.pins.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps contexts of local variables" do + it 'maps contexts of local variables' do var = 'bar' smap = Solargraph::SourceMap.load_string("#{var} = nil") - pin = smap.locals.select{|p| p.name == var}.first + pin = smap.locals.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) smap = Solargraph::SourceMap.load_string("#{var} ||= nil") - pin = smap.locals.select{|p| p.name == var}.first + pin = smap.locals.select { |p| p.name == var }.first expect(pin.context).to be_a(Solargraph::ComplexType) end - it "maps method aliases" do + it 'maps method aliases' do smap = Solargraph::SourceMap.load_string(%( class Foo def bar; end alias baz bar end )) - pin = smap.pins.select{|p| p.path == 'Foo#baz'}.first + pin = smap.pins.select { |p| p.path == 'Foo#baz' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) end - it "maps attribute aliases" do + it 'maps attribute aliases' do smap = Solargraph::SourceMap.load_string(%( class Foo attr_accessor :bar alias baz bar end )) - pin = smap.pins.select{|p| p.path == 'Foo#baz'}.first + pin = smap.pins.select { |p| p.path == 'Foo#baz' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) end - it "maps class method aliases" do + it 'maps class method aliases' do smap = Solargraph::SourceMap.load_string(%( class Foo class << self @@ -767,12 +769,12 @@ def bar; end end end )) - pin = smap.pins.select{|p| p.path == 'Foo.baz'}.first + pin = smap.pins.select { |p| p.path == 'Foo.baz' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) expect(pin.location.range.start.line).to eq(4) end - it "maps method macros" do + it 'maps method macros' do smap = Solargraph::SourceMap.load_string(%( class Foo # @!macro @@ -780,23 +782,23 @@ class Foo def make klass; end end ), 'test.rb') - pin = smap.pins.select{|p| p.path == 'Foo#make'}.first + pin = smap.pins.select { |p| p.path == 'Foo#make' }.first expect(pin.macros).not_to be_empty end - it "maps method directives" do + it 'maps method directives' do smap = Solargraph::SourceMap.load_string(%( class Foo # @!method bar(baz) # @return [String] end ), 'test.rb') - pin = smap.pins.select{|p| p.path == 'Foo#bar'}.first + pin = smap.pins.select { |p| p.path == 'Foo#bar' }.first expect(pin.return_type.tag).to eq('String') expect(pin.location.filename).to eq('test.rb') end - it "maps aliases from alias_method" do + it 'maps aliases from alias_method' do smap = Solargraph::SourceMap.load_string(%( class Foo class << self @@ -805,22 +807,22 @@ def bar; end end end )) - pin = smap.pins.select{|p| p.path == 'Foo.baz'}.first + pin = smap.pins.select { |p| p.path == 'Foo.baz' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) expect(pin.location.range.start.line).to eq(4) end - it "maps aliases with unknown bases" do + it 'maps aliases with unknown bases' do smap = Solargraph::SourceMap.load_string(%( class Foo alias bar baz end )) - pin = smap.pins.select{|p| p.path == 'Foo#bar'}.first + pin = smap.pins.select { |p| p.path == 'Foo#bar' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) end - it "maps aliases to superclass methods" do + it 'maps aliases to superclass methods' do smap = Solargraph::SourceMap.load_string(%( class Sup # My foo method @@ -830,33 +832,33 @@ class Sub < Sup alias bar foo end )) - pin = smap.pins.select{|p| p.path == 'Sub#bar'}.first + pin = smap.pins.select { |p| p.path == 'Sub#bar' }.first expect(pin).to be_a(Solargraph::Pin::MethodAlias) end - it "uses nodes for method parameter assignments" do + it 'uses nodes for method parameter assignments' do smap = Solargraph::SourceMap.load_string(%( class Foo def bar(baz = quz) end end )) - pin = smap.locals.select{|p| p.name == 'baz'}.first + pin = smap.locals.select { |p| p.name == 'baz' }.first # expect(pin.assignment).to be_a(Parser::AST::Node) expect(Solargraph::Parser.is_ast_node?(pin.assignment)).to be(true) end - it "defers resolution of distant alias_method aliases" do + it 'defers resolution of distant alias_method aliases' do smap = Solargraph::SourceMap.load_string(%( class MyClass alias_method :foo, :bar end )) - pin = smap.pins.select{|p| p.is_a?(Solargraph::Pin::MethodAlias)}.first + pin = smap.pins.select { |p| p.is_a?(Solargraph::Pin::MethodAlias) }.first expect(pin).not_to be_nil end - it "maps explicit begin nodes" do + it 'maps explicit begin nodes' do smap = Solargraph::SourceMap.load_string(%( def foo begin @@ -864,11 +866,11 @@ def foo end end )) - pin = smap.pins.select{|p| p.name == '@x'}.first + pin = smap.pins.select { |p| p.name == '@x' }.first expect(pin).not_to be_nil end - it "maps rescue nodes" do + it 'maps rescue nodes' do smap = Solargraph::SourceMap.load_string(%( def foo @x = make_x @@ -876,13 +878,13 @@ def foo @y = y end )) - err_pin = smap.locals{|p| p.name == 'err'}.first + err_pin = smap.locals { |p| p.name == 'err' }.first expect(err_pin).not_to be_nil - var_pin = smap.pins.select{|p| p.name == '@y'}.first + var_pin = smap.pins.select { |p| p.name == '@y' }.first expect(var_pin).not_to be_nil end - it "maps begin/rescue nodes" do + it 'maps begin/rescue nodes' do smap = Solargraph::SourceMap.load_string(%( def foo begin @@ -892,70 +894,70 @@ def foo end end )) - err_pin = smap.locals{|p| p.name == 'err'}.first + err_pin = smap.locals { |p| p.name == 'err' }.first expect(err_pin).not_to be_nil - var_pin = smap.pins.select{|p| p.name == '@y'}.first + var_pin = smap.pins.select { |p| p.name == '@y' }.first expect(var_pin).not_to be_nil end - it "maps classes with long namespaces" do + it 'maps classes with long namespaces' do smap = Solargraph::SourceMap.load_string(%( class Foo::Bar end ), 'test.rb') - pin = smap.pins.select{|p| p.path == 'Foo::Bar'}.first + pin = smap.pins.select { |p| p.path == 'Foo::Bar' }.first expect(pin).not_to be_nil expect(pin.namespace).to eq('Foo') expect(pin.name).to eq('Bar') expect(pin.path).to eq('Foo::Bar') end - it "ignores aliases that do not map to methods or attributes" do - expect { - smap = Solargraph::SourceMap.load_string(%( + it 'ignores aliases that do not map to methods or attributes' do + expect do + Solargraph::SourceMap.load_string(%( class Foo xyz = String alias foo xyz alias_method :foo, :xyz end ), 'test.rb') - }.not_to raise_error + end.not_to raise_error end - it "ignores private_class_methods that do not map to methods or attributes" do - expect { - smap = Solargraph::SourceMap.load_string(%( + it 'ignores private_class_methods that do not map to methods or attributes' do + expect do + Solargraph::SourceMap.load_string(%( class Foo var = some_method private_class_method :var end ), 'test.rb') - }.not_to raise_error + end.not_to raise_error end - it "ignores private_constants that do not map to namespaces or constants" do - expect { - smap = Solargraph::SourceMap.load_string(%( + it 'ignores private_constants that do not map to namespaces or constants' do + expect do + Solargraph::SourceMap.load_string(%( class Foo var = some_method private_constant :var end ), 'test.rb') - }.not_to raise_error + end.not_to raise_error end - it "ignores module_functions that do not map to methods or attributes" do - expect { - smap = Solargraph::SourceMap.load_string(%( + it 'ignores module_functions that do not map to methods or attributes' do + expect do + Solargraph::SourceMap.load_string(%( class Foo var = some_method module_function :var end ), 'test.rb') - }.not_to raise_error + end.not_to raise_error end - it "handles parse directives" do + it 'handles parse directives' do smap = Solargraph::SourceMap.load_string(%( class Foo # @!parse @@ -965,18 +967,18 @@ class Foo expect(smap.pins.map(&:path)).to include('Foo::Bar') end - it "ignores syntax errors in parse directives" do - expect { + it 'ignores syntax errors in parse directives' do + expect do Solargraph::SourceMap.load_string(%( class Foo # @!parse # def end )) - }.not_to raise_error + end.not_to raise_error end - it "sets visibility for symbol parameters" do + it 'sets visibility for symbol parameters' do smap = Solargraph::SourceMap.load_string(%( class Foo def pub; end @@ -987,33 +989,33 @@ def pro; end protected 'pro' end )) - pub = smap.pins.select{|pin| pin.path == 'Foo#pub'}.first + pub = smap.pins.select { |pin| pin.path == 'Foo#pub' }.first expect(pub.visibility).to eq(:public) - bar = smap.pins.select{|pin| pin.path == 'Foo#bar'}.first + bar = smap.pins.select { |pin| pin.path == 'Foo#bar' }.first expect(bar.visibility).to eq(:private) - baz = smap.pins.select{|pin| pin.path == 'Foo#baz'}.first + baz = smap.pins.select { |pin| pin.path == 'Foo#baz' }.first expect(baz.visibility).to eq(:public) - pro = smap.pins.select{|pin| pin.path == 'Foo#pro'}.first + pro = smap.pins.select { |pin| pin.path == 'Foo#pro' }.first expect(pro.visibility).to eq(:protected) end - it "ignores errors in method directives" do - expect { + it 'ignores errors in method directives' do + expect do Solargraph::SourceMap.load_string(%[ class Foo # @!method bar( end ]) - }.not_to raise_error + end.not_to raise_error end - it "handles invalid byte sequences" do - expect { + it 'handles invalid utf8 sequences' do + expect do Solargraph::SourceMap.load(File.join('spec', 'fixtures', 'invalid_utf8.rb')) - }.not_to raise_error + end.not_to raise_error end - it "applies private_class_method to attributes" do + it 'applies private_class_method to attributes' do smap = Solargraph::SourceMap.load_string(%( module Foo class << self @@ -1022,7 +1024,7 @@ class << self private_class_method :bar end )) - pin = smap.pins.select{|pin| pin.path == 'Foo.bar'}.first + pin = smap.pins.select { |pin| pin.path == 'Foo.bar' }.first expect(pin.visibility).to eq(:private) end @@ -1230,7 +1232,7 @@ def bar; end # @!override Foo#bar # return [String] ), 'test.rb') - pins, _locals = Solargraph::SourceMap::Mapper.map(source) + pins, _locals = described_class.map(source) over = pins.select { |pin| pin.is_a?(Solargraph::Pin::Reference::Override) }.first expect(over.name).to eq('Foo#bar') end @@ -1241,7 +1243,7 @@ class Foo def bar(**baz); end end )) - _pins, locals = Solargraph::SourceMap::Mapper.map(source) + _pins, locals = described_class.map(source) param = locals.select { |pin| pin.is_a?(Solargraph::Pin::Parameter) }.first expect(param).to be_kwrestarg end @@ -1252,7 +1254,7 @@ class Foo def bar(baz = {}); end end )) - _pins, locals = Solargraph::SourceMap::Mapper.map(source) + _pins, locals = described_class.map(source) param = locals.select { |pin| pin.is_a?(Solargraph::Pin::Parameter) }.first expect(param).to be_kwrestarg end @@ -1263,7 +1265,7 @@ def bar(baz = {}); end var = 'var' end )) - _pins, locals = Solargraph::SourceMap::Mapper.map(source) + _pins, locals = described_class.map(source) expect(locals).to be_one end @@ -1283,7 +1285,7 @@ class Foo alias_method end )) - pins, locals = Solargraph::SourceMap::Mapper.map(source) + pins, locals = described_class.map(source) expect(pins).to be_one expect(locals).to be_empty end @@ -1352,9 +1354,9 @@ class Foo private_class_method end ) - expect { + expect do Solargraph::SourceMap.load_string(code, 'test.rb') - }.not_to raise_error + end.not_to raise_error end it 'positions method directive pins' do @@ -1431,7 +1433,7 @@ def foo bar:, **splat end it 'gracefully handles misunderstood macros' do - expect { + expect do Solargraph::SourceMap.load_string(%( module Foo # @!macro macro1 @@ -1442,7 +1444,7 @@ module Foo class Bar; end end )) - }.not_to raise_error + end.not_to raise_error end it 'maps autoload paths' do @@ -1570,15 +1572,15 @@ def barbaz; end end it 'handles invalid byte sequences' do - expect { + expect do Solargraph::SourceMap.load('spec/fixtures/invalid_byte.rb') - }.not_to raise_error + end.not_to raise_error end it 'handles invalid byte sequences in stringified node comments' do - expect { + expect do Solargraph::SourceMap.load('spec/fixtures/invalid_node_comment.rb') - }.not_to raise_error + end.not_to raise_error end it 'parses method directives that start with multiple hashes' do diff --git a/spec/source_map/node_processor_spec.rb b/spec/source_map/node_processor_spec.rb index a0ce0bc91..ae34cd49c 100644 --- a/spec/source_map/node_processor_spec.rb +++ b/spec/source_map/node_processor_spec.rb @@ -1,4 +1,6 @@ -describe 'Node processor (generic)' do +# frozen_string_literal: true + +describe Solargraph::Parser::NodeProcessor do it 'maps arg parameters' do map = Solargraph::SourceMap.load_string(%( class Foo diff --git a/spec/source_map_spec.rb b/spec/source_map_spec.rb index 5d587e27c..a5fcea154 100644 --- a/spec/source_map_spec.rb +++ b/spec/source_map_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::SourceMap do - it "locates named path pins" do - map = Solargraph::SourceMap.load_string(%( + it 'locates named path pins' do + map = described_class.load_string(%( class Foo def bar; end end @@ -9,19 +11,21 @@ def bar; end expect(pin.path).to eq('Foo#bar') end - it "queries symbols using fuzzy matching" do - map = Solargraph::SourceMap.load_string(%( + it 'queries symbols using fuzzy matching' do + map = described_class.load_string(%( class FooBar def baz_qux; end end )) - expect(map.query_symbols("foo")).to eq(map.document_symbols) - expect(map.query_symbols("foobar")).to eq(map.document_symbols) - expect(map.query_symbols("bazqux")).to eq(map.document_symbols.select{ |pin_namespace| pin_namespace.name == "baz_qux" }) + expect(map.query_symbols('foo')).to eq(map.document_symbols) + expect(map.query_symbols('foobar')).to eq(map.document_symbols) + expect(map.query_symbols('bazqux')).to eq(map.document_symbols.select { |pin_namespace| + pin_namespace.name == 'baz_qux' + }) end it 'returns all pins, except for references as document symbols' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class FooBar require 'foo' include SomeModule @@ -37,7 +41,7 @@ def baz_qux; end it 'includes convention pins in document symbols' do dummy_convention = Class.new(Solargraph::Convention::Base) do - def local(source_map) + def local source_map source_map.document_symbols # call memoized method Solargraph::Environ.new( @@ -54,7 +58,7 @@ def local(source_map) Solargraph::Convention.register dummy_convention - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class FooBar def baz_qux; end end @@ -65,8 +69,8 @@ def baz_qux; end Solargraph::Convention.unregister dummy_convention end - it "locates block pins" do - map = Solargraph::SourceMap.load_string(%( + it 'locates block pins' do + map = described_class.load_string(%( class Foo 100.times do end @@ -77,7 +81,7 @@ class Foo end it 'scopes local variables correctly from root def methods' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( x = 'string' def foo x @@ -89,7 +93,7 @@ def foo end it 'scopes local variables correctly from class methods' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class Foo x = 'string' def foo @@ -108,7 +112,7 @@ def foo ENV['SOLARGRAPH_ASSERTS'] = 'on' expect do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( Foo.bar += baz ), 'test.rb') loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) @@ -125,7 +129,7 @@ def foo ENV['SOLARGRAPH_ASSERTS'] = 'on' expect do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( Foo.bar ||= baz ), 'test.rb') loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) @@ -142,7 +146,7 @@ def foo ENV['SOLARGRAPH_ASSERTS'] = 'on' expect do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( Foo.bar = baz ), 'test.rb') loc = Solargraph::Location.new('test.rb', Solargraph::Range.from_to(3, 9, 3, 9)) @@ -154,7 +158,7 @@ def foo end it 'scopes local variables correctly in class_eval blocks' do - map = Solargraph::SourceMap.load_string(%( + map = described_class.load_string(%( class Foo; end x = 'y' Foo.class_eval do @@ -163,16 +167,16 @@ class Foo; end end ), 'test.rb') locals = map.locals_at(Solargraph::Location.new('test.rb', Solargraph::Range.from_to(5, 0, 5, 0))).map(&:name) - expect(locals).to eq(['x', 'foo']) + expect(locals).to eq(%w[x foo]) end it 'updates cached inference when the ApiMap changes' do - file1 = Solargraph::SourceMap.load_string(%( + file1 = described_class.load_string(%( def foo '' end ), 'file1.rb') - file2 = Solargraph::SourceMap.load_string(%( + file2 = described_class.load_string(%( foo ), 'file2.rb') @@ -184,7 +188,7 @@ def foo original_api_map_hash = api_map.hash original_source_map_hash = file1.hash - file1 = Solargraph::SourceMap.load_string(%( + file1 = described_class.load_string(%( def foo [] end diff --git a/spec/source_spec.rb b/spec/source_spec.rb index 93624313c..6357924f2 100644 --- a/spec/source_spec.rb +++ b/spec/source_spec.rb @@ -1,13 +1,15 @@ +# frozen_string_literal: true + describe Solargraph::Source do - it "parses code" do + it 'parses code' do code = 'class Foo;def bar;end;end' source = described_class.new(code) expect(source.code).to eq(code) - expect(Solargraph::Parser.is_ast_node?(source.node)).to be_truthy + expect(Solargraph::Parser).to be_is_ast_node(source.node) expect(source).to be_parsed end - it "fixes invalid code" do + it 'fixes invalid code' do code = 'class Foo; def bar; x.' source = described_class.new(code) expect(source.code).to eq(code) @@ -17,7 +19,7 @@ expect(source).not_to be_parsed end - it "finds ranges" do + it 'finds ranges' do code = %( class Foo def bar @@ -29,7 +31,7 @@ def bar expect(source.at(range)).to eq('def bar') end - it "finds nodes" do + it 'finds nodes' do code = 'class Foo;def bar;end;end' source = described_class.new(code) node = source.node_at(0, 0) @@ -38,7 +40,7 @@ def bar expect(node.type).to eq(:def) end - it "synchronizes from incremental updates" do + it 'synchronizes from incremental updates' do code = 'class Foo;def bar;end;end' source = described_class.new(code) updater = Solargraph::Source::Updater.new( @@ -55,19 +57,19 @@ def bar expect(changed.node.children[0].children[1]).to eq(:Food) end - it "synchronizes from full updates" do + it 'synchronizes from full updates' do code1 = 'class Foo;end' code2 = 'class Bar;end' source = described_class.new(code1) updater = Solargraph::Source::Updater.new(nil, 0, [ - Solargraph::Source::Change.new(nil, code2) - ]) + Solargraph::Source::Change.new(nil, code2) + ]) changed = source.synchronize(updater) expect(changed.code).to eq(code2) expect(changed.node.children[0].children[1]).to eq(:Bar) end - it "repairs broken incremental updates" do + it 'repairs broken incremental updates' do code = %( class Foo def bar @@ -89,19 +91,19 @@ def bar expect(changed).to be_repaired end - it "flags irreparable updates" do + it 'flags irreparable updates' do code = 'class Foo;def bar;end;end' source = described_class.new(code) updater = Solargraph::Source::Updater.new(nil, 0, [ - Solargraph::Source::Change.new(nil, 'end;end') - ]) + Solargraph::Source::Change.new(nil, 'end;end') + ]) changed = source.synchronize(updater) expect(changed).to be_parsed expect(changed).to be_repaired end - it "finds references" do - source = Solargraph::Source.load_string(%( + it 'finds references' do + source = described_class.load_string(%( class Foo def bar end @@ -113,46 +115,46 @@ def bar= 𐐀.bar = 1 )) foos = source.references('Foo') - foobacks = foos.map{|f| source.at(f.range)} - expect(foobacks).to eq(['Foo', 'Foo']) + foobacks = foos.map { |f| source.at(f.range) } + expect(foobacks).to eq(%w[Foo Foo]) bars = source.references('bar') - barbacks = bars.map{|b| source.at(b.range)} - expect(barbacks).to eq(['bar', 'bar']) + barbacks = bars.map { |b| source.at(b.range) } + expect(barbacks).to eq(%w[bar bar]) assign_bars = source.references('bar=') - assign_barbacks = assign_bars.map{|b| source.at(b.range)} + assign_barbacks = assign_bars.map { |b| source.at(b.range) } expect(assign_barbacks).to eq(['bar=', 'bar =']) end - it "allows escape sequences incompatible with UTF-8" do - source = Solargraph::Source.new(' + it 'allows escape sequences incompatible with UTF-8' do + source = described_class.new(' x = " Un bUen café \x92" puts x ') expect(source.parsed?).to be(true) end - it "fixes invalid byte sequences in UTF-8 encoding" do - expect { - Solargraph::Source.load('spec/fixtures/invalid_byte.rb') - }.not_to raise_error + it 'fixes invalid byte sequences in UTF-8 encoding' do + expect do + described_class.load('spec/fixtures/invalid_byte.rb') + end.not_to raise_error end - it "loads files with Unicode characters" do - expect { - Solargraph::Source.load('spec/fixtures/unicode.rb') - }.not_to raise_error + it 'loads files with Unicode characters' do + expect do + described_class.load('spec/fixtures/unicode.rb') + end.not_to raise_error end - it "updates itself when code does not change" do - original = Solargraph::Source.load_string('x = y', 'test.rb') + it 'updates itself when code does not change' do + original = described_class.load_string('x = y', 'test.rb') updater = Solargraph::Source::Updater.new('test.rb', 1, []) updated = original.synchronize(updater) expect(original).to be(updated) expect(updated.version).to eq(1) end - it "handles unparseable code" do - source = Solargraph::Source.load_string(%( + it 'handles unparseable code' do + source = described_class.load_string(%( 100.times do |num| )) # @todo Unparseable code results in a nil node for now, but that could @@ -161,9 +163,9 @@ def bar= expect(source.parsed?).to be(false) end - it "finds foldable ranges" do + it 'finds foldable ranges' do # Of the 7 possible ranges, 2 are too short to be foldable - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( =begin Range 1 =end @@ -192,7 +194,7 @@ def range_2 end it 'folds multiline strings' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( a = 1 b = 2 c = 3 @@ -207,7 +209,7 @@ def range_2 end it 'folds multiline arrays' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( a = 1 b = 2 c = 3 @@ -222,7 +224,7 @@ def range_2 end it 'folds multiline hashes' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( a = 1 b = 2 c = 3 @@ -236,8 +238,8 @@ def range_2 expect(source.folding_ranges.first.start.line).to eq(4) end - it "finishes synchronizations for unbalanced lines" do - source1 = Solargraph::Source.load_string('x = 1', 'test.rb') + it 'finishes synchronizations for unbalanced lines' do + source1 = described_class.load_string('x = 1', 'test.rb') source2 = source1.synchronize Solargraph::Source::Updater.new( 'test.rb', 2, @@ -252,21 +254,21 @@ def range_2 expect(source2).to be_synchronized end - it "handles comment arrays that overlap lines" do + it 'handles comment arrays that overlap lines' do # Fixes negative argument error (castwide/solargraph#141) - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( =begin =end y = 1 #foo )) node = source.node_at(3, 0) - expect { + expect do source.comments_for(node) - }.not_to raise_error + end.not_to raise_error end - it "formats comments with multiple hash prefixes" do - source = Solargraph::Source.load_string(%( + it 'formats comments with multiple hash prefixes' do + source = described_class.load_string(%( ## # one # two @@ -274,11 +276,11 @@ class Foo; end )) node = source.node_at(4, 7) comments = source.comments_for(node) - expect(comments.lines.map(&:chomp)).to eq(['one', 'two']) + expect(comments.lines.map(&:chomp)).to eq(%w[one two]) end it 'does not include inner comments' do - source = Solargraph::Source.load_string(%( + source = described_class.load_string(%( # included class Foo # ignored @@ -291,12 +293,12 @@ class Foo end it 'handles long squiggly heredocs' do - source = Solargraph::Source.load('spec/fixtures/long_squiggly_heredoc.rb') + source = described_class.load('spec/fixtures/long_squiggly_heredoc.rb') expect(source.string_ranges).not_to be_empty end it 'handles string array substitutions' do - source = Solargraph::Source.load_string( + source = described_class.load_string( '%W[array of words #{\'with a substitution\'}]' ) expect(source.string_ranges.length).to eq(4) @@ -305,6 +307,6 @@ class Foo it 'handles errors in docstrings' do # YARD has a known problem with empty @overload tags comments = "@overload\n@return [String]" - expect { Solargraph::Source.parse_docstring(comments) }.not_to raise_error + expect { described_class.parse_docstring(comments) }.not_to raise_error end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0a0c1dde4..65d3bb7d4 100755 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'bundler/setup' require 'webmock/rspec' WebMock.disable_net_connect!(allow_localhost: true) @@ -35,14 +37,14 @@ # @param name [String] # @param value [String] -def with_env_var(name, value) - old_value = ENV[name] # Store the old value - ENV[name] = value # Set to new value +def with_env_var name, value + old_value = ENV.fetch(name, nil) # Store the old value + ENV[name] = value # Set to new value begin - yield # Execute the block + yield # Execute the block ensure - ENV[name] = old_value # Restore the old value + ENV[name] = old_value # Restore the old value end end diff --git a/spec/type_checker/levels/alpha_spec.rb b/spec/type_checker/levels/alpha_spec.rb index ac164dec2..aca95b9c3 100644 --- a/spec/type_checker/levels/alpha_spec.rb +++ b/spec/type_checker/levels/alpha_spec.rb @@ -6,6 +6,20 @@ def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :alpha) end + it 'reports use of superclass when subclass is required' do + checker = type_checker(%( + class Sup; end + class Sub < Sup + # @return [Sub] + def foo + Sup.new + end + end + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('does not match inferred type') + end + it 'allows a compatible function call from two distinct types in a union' do checker = type_checker(%( class Foo @@ -122,7 +136,8 @@ def bar end )) - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round on Integer, nil"]) + expect(checker.problems.map(&:message)).to eq(['Foo#bar return type could not be inferred', + 'Unresolved call to round on Integer, nil']) end it 'understands &. in return position' do diff --git a/spec/type_checker/levels/normal_spec.rb b/spec/type_checker/levels/normal_spec.rb index 668e62886..21243d161 100644 --- a/spec/type_checker/levels/normal_spec.rb +++ b/spec/type_checker/levels/normal_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do context 'when checking at normal level' do - def type_checker(code) + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :normal) end @@ -201,7 +203,7 @@ def bar; end expect(checker.problems).to be_empty end - it 'reports unresolved return tags' do + it 'reports unresolved type tags' do checker = type_checker(%( # @type [UnknownClass] x = unknown_method @@ -609,7 +611,7 @@ def bar end it 'verifies block passes in arguments' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( class Foo def map(&block) block.call(100) @@ -624,7 +626,7 @@ def to_s end it 'verifies args and block passes' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( class Foo def map(x, &block) block.call(x) @@ -639,14 +641,14 @@ def to_s end it 'verifies extra block passes in chained calls' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( ''.to_s(&:nil?) ), 'test.rb') expect(checker.problems).to be_empty end it 'verifies extra block variables in calls with args' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( def foo(bar); end foo(1, &block) ), 'test.rb') @@ -654,7 +656,7 @@ def foo(bar); end end it 'verifies splats passed to arguments' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( def foo(bar, baz); end foo(*splat) ), 'test.rb') diff --git a/spec/type_checker/levels/strict_spec.rb b/spec/type_checker/levels/strict_spec.rb index d327199e3..ea6515b80 100644 --- a/spec/type_checker/levels/strict_spec.rb +++ b/spec/type_checker/levels/strict_spec.rb @@ -1,7 +1,9 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do - context 'strict level' do + context 'when at strict level' do # @return [Solargraph::TypeChecker] - def type_checker(code) + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :strict) end @@ -105,7 +107,7 @@ def bar(a); end ), 'test.rb') api_map = Solargraph::ApiMap.load '.' api_map.catalog Solargraph::Bench.new(source_maps: [source_map], external_requires: ['kramdown-parser-gfm']) - checker = Solargraph::TypeChecker.new('test.rb', api_map: api_map, level: :strict) + checker = described_class.new('test.rb', api_map: api_map, level: :strict) expect(checker.problems).to be_empty end @@ -147,18 +149,6 @@ def bar *baz expect(checker.problems).to be_empty end - it 'reports mismatched argument types' do - checker = type_checker(%( - class Foo - # @param baz [Integer] - def bar(baz); end - end - Foo.new.bar('string') - )) - expect(checker.problems).to be_one - expect(checker.problems.first.message).to include('Wrong argument type') - end - it 'reports mismatched argument types in chained calls' do checker = type_checker(%( # @param baz [Integer] @@ -196,7 +186,9 @@ def bar(baz); "foo"; end xit 'complains about calling a non-existent method' - xit 'complains about inserting the wrong type into a tuple slot' do + it 'complains about inserting the wrong type into a tuple slot' do + pending 'Better error message from tuple support' + checker = type_checker(%( # @param a [::Solargraph::Fills::Tuple(String, Integer)] def foo(a) @@ -491,20 +483,6 @@ def bar(baz) expect(checker.problems.first.message).to include('Not enough arguments') end - it 'does not attempt to account for splats' do - checker = type_checker(%( - class Foo - def bar(baz, bing) - end - - def blah(args) - bar *args - end - end - )) - expect(checker.problems).to be_empty - end - it 'does not attempt to account for splats in arg counts' do checker = type_checker(%( class Foo @@ -581,9 +559,7 @@ def bar(baz:, bing:) expect(checker.problems).to be_empty end - it 'requires strict return tags' do - pending 'nil? support in flow sensitive typing' - + it 'does not require nil correctness in return tags when nil is involved and used second in a ternary' do checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] @@ -594,13 +570,10 @@ def bar end end )) - expect(checker.problems).to be_one - expect(checker.problems.first.message).to include('does not match inferred type') + expect(checker.problems.map(&:message)).not_to include('does not match inferred type') end - it 'requires strict return tags' do - pending 'nil? support in flow sensitive typing' - + it 'does not require nil correctness in return tags when nil is involved and used first in a ternary' do checker = type_checker(%( class Foo # The tag is [String] but the inference is [String, nil] @@ -611,8 +584,7 @@ def bar end end )) - expect(checker.problems).to be_one - expect(checker.problems.first.message).to include('does not match inferred type') + expect(checker.problems.map(&:message)).not_to include('does not match inferred type') end it 'validates strict return tags' do @@ -787,7 +759,6 @@ def test(foo: nil) expect(checker.problems).to be_empty end - it 'validates parameters in function calls' do checker = type_checker(%( # @param bar [String] @@ -937,7 +908,7 @@ def foo *path, baz; end expect(checker.problems.map(&:message)).to eq([]) end - it "understands enough of define_method not to think the block is in class scope" do + it 'understands enough of define_method not to think the block is in class scope' do checker = type_checker(%( class Foo def initialize @@ -964,7 +935,7 @@ def bar expect(checker.problems.map(&:message)).to be_empty end - it "Uses flow scope to specialize understanding of cvar types" do + it 'Uses flow scope to specialize understanding of cvar types' do pending 'better cvar support' checker = type_checker(%( @@ -992,10 +963,10 @@ def foo end end )) - expect(checker.problems.map(&:message)).to eq(["Unresolved call to upcase!"]) + expect(checker.problems.map(&:message)).to eq(['Unresolved call to upcase!']) end - it "does not lose track of place and false alarm when using kwargs after a splat" do + it 'does not lose track of place and false alarm when using kwargs after a splat' do checker = type_checker(%( def foo(a, b, c); end def bar(*args, **kwargs, &blk) @@ -1005,7 +976,7 @@ def bar(*args, **kwargs, &blk) expect(checker.problems.map(&:message)).to eq([]) end - it "understands Array#+ overloads" do + it 'understands Array#+ overloads' do checker = type_checker(%( c = ['a'] + ['a'] c @@ -1013,7 +984,7 @@ def bar(*args, **kwargs, &blk) expect(checker.problems.map(&:message)).to eq([]) end - it "understands String#+ overloads" do + it 'understands String#+ overloads' do checker = type_checker(%( detail = '' detail += "foo" @@ -1022,7 +993,7 @@ def bar(*args, **kwargs, &blk) expect(checker.problems.map(&:message)).to eq([]) end - it "understands Enumerable#each via _Each self type" do + it 'understands Enumerable#each via _Each self type' do checker = type_checker(%( class Blah # @param e [Enumerable] diff --git a/spec/type_checker/levels/strong_spec.rb b/spec/type_checker/levels/strong_spec.rb index a2f053ef9..5435058d5 100644 --- a/spec/type_checker/levels/strong_spec.rb +++ b/spec/type_checker/levels/strong_spec.rb @@ -1,9 +1,41 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do - context 'strong level' do - def type_checker(code) + context 'with level set to strong' do + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :strong) end + it 'requires strict return tags when nil is involved and used second in a ternary' do + checker = type_checker(%( + class Foo + # The tag is [String] but the inference is [String, nil] + # + # @return [String] + def bar + false ? 'bar' : nil + end + end + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('does not match inferred type') + end + + it 'requires strict return tags when nil is involved and used first in a ternary' do + checker = type_checker(%( + class Foo + # The tag is [String] but the inference is [String, nil] + # + # @return [String] + def bar + true ? nil : 'bar' + end + end + )) + expect(checker.problems).to be_one + expect(checker.problems.first.message).to include('does not match inferred type') + end + it 'understands self type when passed as parameter' do checker = type_checker(%( class Location @@ -763,7 +795,8 @@ def bar end )) - expect(checker.problems.map(&:message)).to eq(["Foo#bar return type could not be inferred", "Unresolved call to round on Integer, nil"]) + expect(checker.problems.map(&:message)).to eq(['Foo#bar return type could not be inferred', + 'Unresolved call to round on Integer, nil']) end it 'performs simple flow-sensitive typing on lvars' do diff --git a/spec/type_checker/levels/typed_spec.rb b/spec/type_checker/levels/typed_spec.rb index 4add0903d..4b9a3226a 100644 --- a/spec/type_checker/levels/typed_spec.rb +++ b/spec/type_checker/levels/typed_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker do - context 'typed level' do - def type_checker(code) + context 'when level set to typed' do + def type_checker code Solargraph::TypeChecker.load_string(code, 'test.rb', :typed) end @@ -189,21 +191,17 @@ def foo expect(checker.problems).to be_empty end - it 'reports superclasses of return types' do - # @todo This test might be invalid. There are use cases where inheritance - # between inferred and expected classes should be acceptable in either - # direction. - # checker = type_checker(%( - # class Sup; end - # class Sub < Sup - # # @return [Sub] - # def foo - # Sup.new - # end - # end - # )) - # expect(checker.problems).to be_one - # expect(checker.problems.first.message).to include('does not match inferred type') + it 'allows superclass of return types' do + checker = type_checker(%( + class Sup; end + class Sub < Sup + # @return [Sub] + def foo + Sup.new + end + end + )) + expect(checker.problems.map(&:message)).not_to include('does not match inferred type') end it 'validates generic subclasses of return types' do diff --git a/spec/type_checker/rules_spec.rb b/spec/type_checker/rules_spec.rb index ded5302fa..1ecc30714 100644 --- a/spec/type_checker/rules_spec.rb +++ b/spec/type_checker/rules_spec.rb @@ -1,6 +1,8 @@ +# frozen_string_literal: true + describe Solargraph::TypeChecker::Rules do it 'sets normal rules' do - rules = Solargraph::TypeChecker::Rules.new(:normal, {}) + rules = described_class.new(:normal, {}) expect(rules.ignore_all_undefined?).to be(true) expect(rules.must_tag_or_infer?).to be(false) expect(rules.require_type_tags?).to be(false) @@ -9,7 +11,7 @@ end it 'sets typed rules' do - rules = Solargraph::TypeChecker::Rules.new(:typed, {}) + rules = described_class.new(:typed, {}) expect(rules.ignore_all_undefined?).to be(true) expect(rules.must_tag_or_infer?).to be(false) expect(rules.require_type_tags?).to be(false) @@ -18,7 +20,7 @@ end it 'sets strict rules' do - rules = Solargraph::TypeChecker::Rules.new(:strict, {}) + rules = described_class.new(:strict, {}) expect(rules.ignore_all_undefined?).to be(false) expect(rules.must_tag_or_infer?).to be(true) expect(rules.require_type_tags?).to be(false) @@ -27,7 +29,7 @@ end it 'sets strong rules' do - rules = Solargraph::TypeChecker::Rules.new(:strong, {}) + rules = described_class.new(:strong, {}) expect(rules.ignore_all_undefined?).to be(false) expect(rules.must_tag_or_infer?).to be(true) expect(rules.require_type_tags?).to be(true) diff --git a/spec/type_checker_spec.rb b/spec/type_checker_spec.rb index 33c2125e7..3113896d2 100644 --- a/spec/type_checker_spec.rb +++ b/spec/type_checker_spec.rb @@ -1,17 +1,19 @@ +# frozen_string_literal: true + require 'timeout' describe Solargraph::TypeChecker do it 'does not raise errors checking unparsed sources' do - expect { - checker = Solargraph::TypeChecker.load_string(%( + expect do + checker = described_class.load_string(%( foo{ )) checker.problems - }.not_to raise_error + end.not_to raise_error end it 'ignores tagged problems' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( NotAClass # @sg-ignore @@ -21,7 +23,7 @@ end it 'uses caching in Solargraph::Chain to handle a degenerate case' do - checker = Solargraph::TypeChecker.load_string(%( + checker = described_class.load_string(%( def documentation @documentation = "a" @documentation += "b" @@ -38,7 +40,7 @@ def documentation end ), nil, :strict) timed_out = true - Timeout::timeout(5) do # seconds + Timeout.timeout(5) do # seconds checker.problems timed_out = false end diff --git a/spec/workspace/config_spec.rb b/spec/workspace/config_spec.rb index 9b14d4337..7bb04ba7a 100644 --- a/spec/workspace/config_spec.rb +++ b/spec/workspace/config_spec.rb @@ -1,51 +1,54 @@ +# frozen_string_literal: true + require 'fileutils' require 'tmpdir' describe Solargraph::Workspace::Config do - let(:dir_path) { File.realpath(Dir.mktmpdir) } - after(:each) { FileUtils.remove_entry(dir_path) } + let(:dir_path) { File.realpath(Dir.mktmpdir) } + + after { FileUtils.remove_entry(dir_path) } - it "includes .rb files by default" do + it 'includes .rb files by default' do file = File.join(dir_path, 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).to include(file) end - it "includes .rb files in subdirectories by default" do + it 'includes .rb files in subdirectories by default' do Dir.mkdir(File.join(dir_path, 'lib')) file = File.join(dir_path, 'lib', 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).to include(file) end - it "excludes test directories by default" do + it 'excludes test directories by default' do Dir.mkdir(File.join(dir_path, 'test')) file = File.join(dir_path, 'test', 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).not_to include(file) end - it "excludes spec directories by default" do + it 'excludes spec directories by default' do Dir.mkdir(File.join(dir_path, 'spec')) file = File.join(dir_path, 'spec', 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).not_to include(file) end - it "excludes vendor directories by default" do + it 'excludes vendor directories by default' do Dir.mkdir(File.join(dir_path, 'vendor')) file = File.join(dir_path, 'vendor', 'file.rb') File.write(file, 'exit') - config = Solargraph::Workspace::Config.new(dir_path) + config = described_class.new(dir_path) expect(config.calculated).not_to include(file) end - it "includes base reporters by default" do - config = Solargraph::Workspace::Config.new(dir_path) + it 'includes base reporters by default' do + config = described_class.new(dir_path) expect(config.reporters).to include('rubocop') expect(config.reporters).to include('require_not_found') end diff --git a/spec/workspace_spec.rb b/spec/workspace_spec.rb index 78b71ce3b..0924cd707 100644 --- a/spec/workspace_spec.rb +++ b/spec/workspace_spec.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + require 'fileutils' require 'tmpdir' @@ -6,15 +8,15 @@ let(:dir_path) { File.realpath(Dir.mktmpdir) } let(:file_path) { File.join(dir_path, 'file.rb') } - before(:each) { File.write(file_path, 'exit') } - after(:each) { FileUtils.remove_entry(dir_path) } + before { File.write(file_path, 'exit') } + after { FileUtils.remove_entry(dir_path) } - it "loads sources from a directory" do + it 'loads sources from a directory' do expect(workspace.filenames).to include(file_path) expect(workspace.has_file?(file_path)).to be(true) end - it "ignores non-Ruby files by default" do + it 'ignores non-Ruby files by default' do not_ruby = File.join(dir_path, 'not_ruby.txt') File.write not_ruby, 'text' @@ -22,14 +24,14 @@ expect(workspace.filenames).not_to include(not_ruby) end - it "does not merge non-workspace sources" do + it 'does not merge non-workspace sources' do source = Solargraph::Source.load_string('exit', 'not_ruby.txt') workspace.merge source expect(workspace.filenames).not_to include(source.filename) end - it "updates sources" do + it 'updates sources' do original = workspace.source(file_path) updated = Solargraph::Source.load_string('puts "updated"', file_path) workspace.merge updated @@ -39,7 +41,7 @@ expect(workspace.source(file_path)).to eq(updated) end - it "removes deleted sources" do + it 'removes deleted sources' do expect(workspace.filenames).to include(file_path) original = workspace.source(file_path) @@ -49,37 +51,39 @@ expect(workspace.filenames).not_to include(file_path) end - it "raises an exception for workspace size limits" do - config = double(:config, calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) + it 'raises an exception for workspace size limits' do + config = instance_double(Solargraph::Workspace::Config, + calculated: Array.new(Solargraph::Workspace::Config::MAX_FILES + 1), max_files: Solargraph::Workspace::Config::MAX_FILES) - expect { - Solargraph::Workspace.new('.', config) - }.to raise_error(Solargraph::WorkspaceTooLargeError) + expect do + described_class.new('.', config) + end.to raise_error(Solargraph::WorkspaceTooLargeError) end - it "allows for unlimited files in config" do + it 'allows for unlimited files in config' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, '') calculated = Array.new(Solargraph::Workspace::Config::MAX_FILES + 1) { gemspec_file } # @todo Mock reveals tight coupling - config = double(:config, calculated: calculated, max_files: 0, allow?: true, require_paths: [], plugins: []) - expect { - Solargraph::Workspace.new('.', config) - }.not_to raise_error + config = instance_double(Solargraph::Workspace::Config, calculated: calculated, max_files: 0, allow?: true, + require_paths: [], plugins: []) + expect do + described_class.new('.', config) + end.not_to raise_error end - it "detects gemspecs in workspaces" do + it 'detects gemspecs in workspaces' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, '') expect(workspace.gemspec?).to be(true) expect(workspace.gemspec_files).to eq([gemspec_file]) end - it "generates default require path" do + it 'generates default require path' do expect(workspace.require_paths).to eq([File.join(dir_path, 'lib')]) end - it "generates require paths from gemspecs" do + it 'generates require paths from gemspecs' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, %( Gem::Specification.new do |s| @@ -93,7 +97,7 @@ expect(workspace.require_paths).to eq([File.join(dir_path, 'other_lib')]) end - it "rescues errors in gemspecs" do + it 'rescues errors in gemspecs' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, %( raise 'Error' @@ -101,7 +105,7 @@ expect(workspace.require_paths).to eq([File.join(dir_path, 'lib')]) end - it "rescues syntax errors in gemspecs" do + it 'rescues syntax errors in gemspecs' do gemspec_file = File.join(dir_path, 'test.gemspec') File.write(gemspec_file, %( 123. @@ -109,7 +113,7 @@ expect(workspace.require_paths).to eq([File.join(dir_path, 'lib')]) end - it "detects locally required paths" do + it 'detects locally required paths' do required_file = File.join(dir_path, 'lib', 'test.rb') Dir.mkdir(File.join(dir_path, 'lib')) File.write(required_file, 'exit') @@ -122,23 +126,24 @@ expect(workspace.would_require?('emptydir')).to be(false) end - it "uses configured require paths" do - workspace = Solargraph::Workspace.new('spec/fixtures/workspace') + it 'uses configured require paths' do + workspace = described_class.new('spec/fixtures/workspace') expect(workspace.require_paths).to eq([File.absolute_path('spec/fixtures/workspace/lib'), File.absolute_path('spec/fixtures/workspace/ext')]) end it 'ignores gemspecs in excluded directories' do # vendor/**/* is excluded by default - workspace = Solargraph::Workspace.new('spec/fixtures/vendored') + workspace = described_class.new('spec/fixtures/vendored') expect(workspace.require_paths).to eq([File.absolute_path('spec/fixtures/vendored/lib')]) end it 'rescues errors loading files into sources' do - config = double(:Config, directory: './path', calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) - expect { - Solargraph::Workspace.new('./path', config) - }.not_to raise_error + config = instance_double(Solargraph::Workspace::Config, directory: './path', + calculated: ['./path/does_not_exist.rb'], max_files: 5000, require_paths: [], plugins: []) + expect do + described_class.new('./path', config) + end.not_to raise_error end describe '#cache_all_for_workspace!' do @@ -161,20 +166,5 @@ expect(Solargraph::PinCache).to have_received(:cache_core).with(out: nil) end - - it 'caches gems' do - gemspec = instance_double(Gem::Specification, name: 'test_gem', version: '1.0.0') - allow(Gem::Specification).to receive(:to_a).and_return([gemspec]) - allow(pin_cache).to receive(:cached?).and_return(false) - allow(pin_cache).to receive(:cache_all_stdlibs).with(out: nil, rebuild: false) - - allow(Solargraph::PinCache).to receive_messages(core?: true, - possible_stdlibs: []) - - workspace.cache_all_for_workspace!(nil, rebuild: false) - - expect(pin_cache).to have_received(:cache_gem).with(gemspec: gemspec, out: nil, - rebuild: false) - end end end diff --git a/spec/yard_map/mapper/to_method_spec.rb b/spec/yard_map/mapper/to_method_spec.rb index c90fe75ed..c3a46b914 100644 --- a/spec/yard_map/mapper/to_method_spec.rb +++ b/spec/yard_map/mapper/to_method_spec.rb @@ -1,39 +1,41 @@ +# frozen_string_literal: true + describe Solargraph::YardMap::Mapper::ToMethod do - let(:code_object) { + let(:code_object) do namespace = YARD::CodeObjects::ModuleObject.new(nil, 'Example') YARD::CodeObjects::MethodObject.new(namespace, 'foo') - } + end it 'parses args' do - code_object.parameters = [["bar", nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + code_object.parameters = [['bar', nil]] + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:arg) - expect(param.name).to eq("bar") - expect(param.full).to eq("bar") + expect(param.name).to eq('bar') + expect(param.full).to eq('bar') end it 'parses optargs' do - code_object.parameters = [["bar", "'baz'"]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + code_object.parameters = [['bar', "'baz'"]] + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:optarg) - expect(param.name).to eq("bar") + expect(param.name).to eq('bar') expect(param.full).to eq("bar = 'baz'") end it 'parses kwargs' do - code_object.parameters = [["bar:", nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + code_object.parameters = [['bar:', nil]] + pin = described_class.make(code_object) param = pin.parameters.first expect(param.name).to eq('bar') expect(param.decl).to be(:kwarg) - expect(param.full).to eq("bar:") + expect(param.full).to eq('bar:') end it 'parses kwoptargs' do - code_object.parameters = [["bar:", "'baz'"]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + code_object.parameters = [['bar:', "'baz'"]] + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:kwoptarg) expect(param.name).to eq('bar') @@ -41,43 +43,43 @@ end it 'parses restargs' do - code_object.parameters = [["*bar", nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + code_object.parameters = [['*bar', nil]] + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:restarg) expect(param.name).to eq('bar') - expect(param.full).to eq("*bar") + expect(param.full).to eq('*bar') end it 'parses kwrestargs' do - code_object.parameters = [["**bar", nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + code_object.parameters = [['**bar', nil]] + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:kwrestarg) expect(param.name).to eq('bar') - expect(param.full).to eq("**bar") + expect(param.full).to eq('**bar') end it 'parses blockargs' do - code_object.parameters = [["&bar", nil]] - pin = Solargraph::YardMap::Mapper::ToMethod.make(code_object) + code_object.parameters = [['&bar', nil]] + pin = described_class.make(code_object) param = pin.parameters.first expect(param.decl).to be(:blockarg) expect(param.name).to eq('bar') - expect(param.full).to eq("&bar") + expect(param.full).to eq('&bar') end it 'parses undeclared but typed blockargs' do pending('block args coming from YARD alone') code_object.parameters = [] - code_object.docstring = <